使用Moq和EF6进行单元测试

时间:2015-01-23 10:54:26

标签: c# asp.net unit-testing testing asp.net-web-api

我已经为我的服务层构建了单元测试。我没有使用Mock,因为我认为,因为你要添加/删除/查询数据库,为什么查询模拟因为结果可能不同,但这不是我要问的。

现在我正在使用Moq来测试我的web api层。我认为这很好,就好像我的所有测试都在服务层上传递一样,可以模拟测试web api的服务。

我已经设法为我的GetAsync方法编写了一个测试,它运行良好,就像这样

以下是控制器:

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

    return Ok(model);
}

以下是测试:

[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);
}

我相信我写得正确,似乎有效。现在我想测试我的PostAsync方法来添加项目。控制器如下所示:

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));
    }

以下是测试:

[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);
}

现在当我运行它时,我收到一个错误:

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

所以我改变了我的控制器并测试了它,试图模仿它。

测试代码:

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));
}

控制器代码:

[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);
}

现在我收到一条错误说:

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

请有人请,请帮助我。我确信我在所有测试中都正确使用Mock,但单元测试对我来说是新的,所以也许我只是做得不对。

2 个答案:

答案 0 :(得分:4)

使用Moq,您只能模拟virtual/absrtact个成员。 RequestUri不是HttpRequestMessage的虚拟成员,因此出现错误消息。

你应该能够直接新建一个HttpRequestMessage而不会嘲笑它并传递它。

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;

答案 1 :(得分:0)

奈德的回答是正确的。 Moq是一个约束模拟库,这意味着它会生成在运行时模拟的类的动态子类。如果未在模拟类中将这些子类声明为虚拟,则这些子类不能覆盖这些方法。您可以在the art of unit testing中找到有关受约束与不受约束的模拟库的更多信息。

这就是为什么使用mockist风格的单元测试的人更喜欢模仿接口而不是具体的类,因为生成的模拟子类可以轻松地覆盖(或者更确切地说,实现)接口上的方法。