Skip to content

ruizdiazfran/CleanAndTestable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CleanAndTestable

A project to show how to put in place a simple and testable C# solution with some useful patterns like CQS, UnitOfWork, Mediator, Dependency Injection Pattern, Composition Root and so on.

#Goals Our motto for testing is "low ceremony" and "adhere to application".

Heavily inspired from https://vimeo.com/68390508 but with some difference because Moq is not used during test phase. Insted of Moq we use the real IOC container with specific overrides for some infrastructure (like Db or WebApi endpoint). Low ceremony.

Below the code for a simple controller:

[RoutePrefix("api/thing")]
public class ThingController : ApiController
{
    private readonly IMediator _mediator;

    public ThingController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    // ... other methods omitted

    [Route("")]
    public async Task<IHttpActionResult> Post([FromBody] ThingCommand.Create input)
    {
        await _mediator.SendAsync(input);

        return CreatedAtRoute("ThingDetail",new {id= input.Id},input);
    }

    [Route("{id}")]
    public async Task<IHttpActionResult> Delete([FromUri] ThingCommand.Delete input)
    {
        try
        {
            await _mediator.SendAsync(input);
        }
        catch (EntityNotFound)
        {
            return NotFound();
        }

        return Ok();
    }
}

Code for a db integration test.

public class ThingSpecs : SpecsForDb
{
    private readonly IMediator _mediator;

    public ThingTests(IMediator mediator)
    {
        _mediator = mediator;
    }

    public void Should_create(ThingCommand.Create request)
    {
        //  Arrange

        //  Act
        Persist(() => _mediator.SendAsync(request).Wait());

        //  Assert
        Do(db => db.Things.AnyAsync(_ => _.Id == request.Id).Result.ShouldBeTrue());
    }

    public void Should_delete(ThingCommand.Delete request)
    {
        //  Arrange
        request.Id = "my-thirdy";

        //  Act
        Persist(()=>_mediator.SendAsync(request).Wait());

        //  Assert
        Do(db => db.Things.AnyAsync(_ => _.Id == request.Id).Result.ShouldBeFalse());
    }
}

Code for an api integration test.

public class ThingApiSpecs : IDisposable
{
    private readonly HttpClient _httpClient;

    public ThingApiSpecs(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public void Should_create(ThingCommand.Create request)
    {
        //  Arrange

        //  Act
        var response = _httpClient.PostAsJsonAsync($"/api/thing",request).Result;

        //  Assert
        response.StatusCode.ShouldEqual(HttpStatusCode.Created);
        response.Headers.Location.AbsoluteUri.ShouldEqual($"http://localhost/api/thing/{request.Id}");
    }

    public void Should_delete(ThingCommand.Delete request)
    {
        //  Arrange
        request.Id = "my-thirdy";

        //  Act
        var response = _httpClient.DeleteAsync($"/api/thing/{request.Id}").Result;

        //  Assert
        response.StatusCode.ShouldEqual(HttpStatusCode.OK);
    }
}

Not so different ?

Testing in memory

Thanks to powerful Microsoft.Owin.Testing library, it's possible test in easy way the http pipeline. See some detail here

public class HttpPipelineSpecs
{
    public void Should_get_same_service_from_owin_context_and_request()
    {
        //  Arrange
        Action<HttpRequestMessage> assert = _ => 
            _.GetOwinContext().Get<ILifetimeScope>("autofac:OwinLifetimeScope").Resolve<IUnitOfWork>()
            .ShouldBeSameAs(_.GetDependencyScope().GetService(typeof(IUnitOfWork)));

        var httpClient = new HttpConfiguration().ToHttpClient(new InspectHttpRequestMessageHandler(assert));

        //  Act
        var response = httpClient.GetAsync("/foo").Result;

        //  Assert
        response.StatusCode.ShouldEqual(HttpStatusCode.OK);

        httpClient.Dispose();
    }
}

public class InspectHttpRequestMessageHandler : DelegatingHandler
{
    readonly Action<HttpRequestMessage> _assert;

    public InspectHttpRequestMessageHandler(Action<HttpRequestMessage> assert)
    {
        _assert = assert;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        _assert(request);

        return Task.FromResult( request.CreateResponse(HttpStatusCode.OK) );
    }
}

#Technology stack

#Requirements SQL Server LocalDB 2012 link to download

<add name="ThingDbContext"
         connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\Thing.mdf;Integrated Security=True"
         providerName="System.Data.SqlClient" />

SQL Server LocalDB 2014 link to download

<add name="ThingDbContext"
         connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Thing.mdf;Integrated Security=True"
         providerName="System.Data.SqlClient" />

About

A project to show how to put in place a simple and testable C# solution with some useful patterns like CQS, UnitOfWork, Mediator and so on.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages