使用 Moq 和 EF6 进行单元测试
Unit testing with Moq and EF6
我已经为我的服务层构建了单元测试。我没有使用 Mock,因为我认为既然你是 adding/deleting/querying 一个数据库,为什么要查询 mock 因为结果可能不同,但这不是我要问的。
现在我正在使用 Moq 来测试我的网络 api 层。我认为这很好,好像我所有的测试都在服务层上通过了,模拟服务来测试网络就可以了 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
,但单元测试对我来说是新手,所以也许我只是没有做正确的事。
使用 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;
Ned 的回答是正确的。 Moq 是一个 constrained 模拟库,这意味着它会生成您在运行时模拟的 classes 的动态子classes。如果这些 subclasses 没有在 mocked class 中声明为 virtual,则它们不能覆盖方法。您可以在 the art of unit testing.
中找到有关受限和不受限模拟库的更多信息
这就是为什么使用 mockist 风格的单元测试的人更喜欢模拟接口而不是具体的 classes,因为生成的 mock subclasses 可以很容易地覆盖(或者更确切地说,实现) 接口上的方法。
我已经为我的服务层构建了单元测试。我没有使用 Mock,因为我认为既然你是 adding/deleting/querying 一个数据库,为什么要查询 mock 因为结果可能不同,但这不是我要问的。
现在我正在使用 Moq 来测试我的网络 api 层。我认为这很好,好像我所有的测试都在服务层上通过了,模拟服务来测试网络就可以了 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
,但单元测试对我来说是新手,所以也许我只是没有做正确的事。
使用 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;
Ned 的回答是正确的。 Moq 是一个 constrained 模拟库,这意味着它会生成您在运行时模拟的 classes 的动态子classes。如果这些 subclasses 没有在 mocked class 中声明为 virtual,则它们不能覆盖方法。您可以在 the art of unit testing.
中找到有关受限和不受限模拟库的更多信息这就是为什么使用 mockist 风格的单元测试的人更喜欢模拟接口而不是具体的 classes,因为生成的 mock subclasses 可以很容易地覆盖(或者更确切地说,实现) 接口上的方法。