【问题标题】:Unit testing with Moq and EF6使用 Moq 和 EF6 进行单元测试
【发布时间】:2015-01-23 10:54:26
【问题描述】:

我已经为我的服务层构建了单元测试。我没有使用 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,但是单元测试对我来说是新的,所以也许我只是没有做对。

【问题讨论】:

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


    【解决方案1】:

    使用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;
    

    【讨论】:

    • 似乎工作,我的代码现在是 var actionResult = controller.PostAsync(new ContentModel { Heading = "New Heading" }, new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod .post, "localhost/")).Result;
    • 这是正确的。但是您也不需要该方法的单独测试版本。使用 controller.request = new HttpRequestMessage() ... 创建请求后,您可以直接在控制器上设置请求
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-24
      • 2013-06-26
      • 1970-01-01
      • 1970-01-01
      • 2018-03-29
      • 1970-01-01
      相关资源
      最近更新 更多