简体   繁体   English

Moq和EF6进行单元测试

[英]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. 我还没有使用过Mock,因为自从您添加/删除/查询数据库以来,为什么查询一个模拟对象的结果可能会有所不同,但这不是我要的。

Now I am using Moq to test my web api layer. 现在,我正在使用Moq来测试我的Web api层。 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. 我认为这很好,就好像我的所有测试都通过了服务层一样,可以通过模拟服务来测试Web api很好。

I have managed to write a test for my GetAsync method and it works all fine, like so 我已经设法为我的GetAsync方法编写了一个测试,并且一切正常,就像这样

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. 现在,我想测试我的PostAsync方法来添加一个项目。 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. 我确定我在所有测试中都正确使用了Mock ,但是单元测试对我来说是新手,所以也许我只是做的不好。

With Moq you can only mock virtual/absrtact members. 使用Moq您只能模拟virtual/absrtact成员。 The RequestUri is not a virtual member of HttpRequestMessage , hence the error message. RequestUri不是HttpRequestMessage的虚拟成员,因此错误消息。

You should be able to just new a HttpRequestMessage directly without mocking it and pass that in. 您应该能够直接新建一个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;

Ned's answer is correct. 内德的答案是正确的。 Moq is a constrained mocking library, meaning that it generates dynamic subclasses of the classes you mock at runtime. Moq是一个受约束的模拟库,这意味着它会在运行时生成您模拟的类的动态子类。 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. 这就是为什么使用模拟风格的单元测试的人更喜欢针对接口而不是具体的类进行模拟,因为生成的模拟子类可以轻松地覆盖(或更确切地说,实现)接口上的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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