简体   繁体   中英

Unit testing with Moq and EF6

I have built unit testing for my service layer. I have not used Mock as I think that since you are adding/deleting/querying a database, why query a mock as the results could be different, but that isn't what I am asking.

Now I am using Moq to test my web api layer. I think that this is fine, as if all my tests pass on the service layer, it is fine to mock the services to test the web api.

I have managed to write a test for my GetAsync method and it works all fine, like so

Here is the controller:

public async Task<IHttpActionResult> GetAsync(long id)
{
    Content content = await _service.GetAsync(id);
    ContentModel model = Mapper.Map<ContentModel>(content);

    return Ok(model);
}

Here is the test:

[TestMethod]
public void Content_GetAsync()
{
    // arrange
    var mockService = new Mock<IContentService>();
    mockService.Setup(x => x.GetAsync(4))
        .ReturnsAsync(new Content
        {
            Id = 4
        });

    // setup automapper
    AutoMapperConfig.RegisterMappings();

    // act
    var controller = new ContentController(mockService.Object);
    var actionResult = controller.GetAsync(4).Result;
    var contentResult = actionResult as OkNegotiatedContentResult<ContentModel>;

    // assert
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Content);
    Assert.AreEqual(4, contentResult.Content.Id);
}

I believe I wrote this correctly, and it seems to work. Now I would like to test my PostAsync method to add an item. The controller looks like this:

public async Task<IHttpActionResult> PostAsync(ContentModel model)
    {
        Content content = Mapper.Map<Content>(model);

        await _service.AddAsync(content);

        return Created<ContentModel>(Request.RequestUri, Mapper.Map<ContentModel>(content));
    }

And here is the test:

[TestMethod]
public void Content_PostAsync()
{
    var mockService = new Mock<IContentService>();
    mockService.Setup(e => e.AddAsync(new Content()))
        .ReturnsAsync(1);

    // setup automapper
    AutoMapperConfig.RegisterMappings();

    // act
    var controller = new ContentController(mockService.Object);
    var actionResult = controller.PostAsync(new ContentModel {
        Heading = "New Heading"
    }).Result;
    var contentResult = actionResult as CreatedAtRouteNegotiatedContentResult<ContentModel>;

    // assert
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Content);
    Assert.AreEqual("New Heading", contentResult.Content.Heading);
}

Now when I run this, I get an error:

null reference exception.  "Request" from the Request.RequestUri is null.

So I changed my controller and tests to this, to try and mock it.

Test code:

public Task<IHttpActionResult> PostAsync(ContentModel model)
{
    return PostAsync(model, Request);
}

/// Unit testable version of above.  Cannot be accessed by users              
[NonAction]
public async Task<IHttpActionResult> PostAsync(ContentModel model, System.Net.Http.HttpRequestMessage request)
{
    Content content = Mapper.Map<Content>(model);

    await _service.AddAsync(content);

    return Created<ContentModel>(request.RequestUri, Mapper.Map<ContentModel>(content));
}

Controller code:

[TestMethod]
public void Content_PostAsync()
{
    // arrange
    var mockRequest = new Mock<System.Net.Http.HttpRequestMessage>();
    mockRequest.Setup(e => e.RequestUri)
        .Returns(new Uri("http://localhost/"));

    var mockService = new Mock<IContentService>();
    mockService.Setup(e => e.AddAsync(new Content()))
        .ReturnsAsync(1);

    // setup automapper
    AutoMapperConfig.RegisterMappings();

    // act
    var controller = new ContentController(mockService.Object);
    var actionResult = controller.PostAsync(new ContentModel {
        Heading = "New Heading"
    }, mockRequest.Object).Result;
    var contentResult = actionResult as CreatedAtRouteNegotiatedContentResult<ContentModel>;

    // assert
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Content);
    Assert.AreEqual("New Heading", contentResult.Content.Heading);
}

Now I get an error saying:

Invalid setup on a non-virtual (overridable in VB) member: e => e.RequestUri

Can someone please, please help me with this. I am sure I am using Mock correctly in all tests, but unit testing is new to me, so maybe I am just not doing something right.

With Moq you can only mock virtual/absrtact members. The RequestUri is not a virtual member of HttpRequestMessage , hence the error message.

You should be able to just new a HttpRequestMessage directly without mocking it and pass that in.

var request = System.Net.Http.HttpRequestMessage>();
request.RequestUri = new Uri("http://localhost/");

// act
var controller = new ContentController(mockService.Object);
var actionResult = controller.PostAsync(new ContentModel {
    Heading = "New Heading"
}, request).Result;

Ned's answer is correct. Moq is a constrained mocking library, meaning that it generates dynamic subclasses of the classes you mock at runtime. These subclasses cannot override methods if they are not declared virtual in the mocked class. You can find more information on constrained vs. unconstrained mocking libraries in the art of unit testing .

That's why people that use a mockist style of unit testing prefer to mock against interfaces instead of concrete classes, as the generated mock subclasses can easily override (or rather, implement) the methods on the interface.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM