我已经为我的服务层构建了单元测试。我没有使用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
,但单元测试对我来说是新的,所以也许我只是做得不对。
答案 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风格的单元测试的人更喜欢模仿接口而不是具体的类,因为生成的模拟子类可以轻松地覆盖(或者更确切地说,实现)接口上的方法。