为 ASP .NET MVC 创建单元测试时出现问题
Problems in creating unit test for ASP .NET MVC
我正在为我的 ASP .NET MVC 控制器 class 创建一些单元测试,我 运行 遇到了一些非常严重的 运行ge 错误:
我的控制器代码如下:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal)
{
var selectedJournal = Mapper.Map<JournalViewModel, Journal>(journal);
var opStatus = _journalRepository.DeleteJournal(selectedJournal);
if (!opStatus.Status)
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
return RedirectToAction("Index");
}
我的测试代码如下:
[TestMethod]
public void Delete_Journal()
{
// Arrange
// Simulate PDF file
HttpPostedFileBase mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
JournalViewModel journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
Status = true
});
// Act
PublisherController controller = new PublisherController(journalRepository, membershipRepository);
RedirectToRouteResult result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}
问题 1 - 映射异常:
每次我 运行 我的测试都会收到以下异常:
Test Name: Delete_Journal Test
FullName: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
Test Source: \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs
: line 132
Test Outcome: Failed Test Duration: 0:00:00,3822468
Result StackTrace: at
Journals.Web.Controllers.PublisherController.Delete(JournalViewModel
journal) in
\Source\Journals.Web\Controllers\PublisherController.cs:line 81 at
Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal()
in
\Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:line
156 Result Message: Test method
Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
threw exception: AutoMapper.AutoMapperMappingException: Missing type
map configuration or unsupported mapping.
Mapping types: JournalViewModel -> Journal
Journals.Model.JournalViewModel -> Journals.Model.Journal
Destination path: Journal
Source value: Journals.Model.JournalViewModel
classes JournalViewModel
和 Journal
之间似乎存在映射问题,但我不知道那是哪里。我将此代码添加到 Global.asax.cs
中的 Application_Start
:
Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();
并且从 Journal
映射到 JournalViewModel
正在工作。
最后我尝试添加 Mapper.CreateMap<JournalViewModel, Journal>();
作为 Delete
方法的第一行然后一切正常,但我不确定为什么。
问题 2 - HTML异常
一旦映射为 运行 上面的解决方法,我就会遇到一个问题,即 var opStatus = _journalRepository.DeleteJournal(selectedJournal);
中的 属性 Status
始终为 false,即使我使用了 Mock覆盖它并使其始终为真。这会导致抛出不应发生的 HTML 异常。
编辑
我在 Application_Start 中更改为:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
但我仍然有同样的错误。
编辑 - 问题 2 已解决
原来是我忘记将映射添加到我的单元测试中 class,所以我做了以下操作:
[TestInitialize]
public void TestSetup()
{
// Create necessary mappings
Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();
//...other code omitted for brevity
}
事实证明,这就是问题的根源。我认为由于 Global.asax.cs Application_Start() 从未在单元测试中被调用,所以映射从未被创建,所以我不得不在单元测试初始化中自己做这件事。
问题 1
Automapper 有两个 Static and Instance API。您应该考虑将实例 API 与 IMapper
一起使用并将其注入您的控制器。
public class PublisherController : Controller {
private readonly IMapper mapper;
public PublisherController(IJournalRepository journalRepository, IMembershipRepositry membershipRepository, IMapper mapper) {
//...other code omitted for brevity
this.mapper = mapper;
}
//...other code omitted for brevity
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal) {
var selectedJournal = mapper.Map<JournalViewModel, Journal>(journal);
var opStatus = _journalRepository.DeleteJournal(selectedJournal);
if (!opStatus.Status)
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
return RedirectToAction("Index");
}
}
这样可以根据需要更好地 mocking/faking/configuration 映射。您应该确保配置 IMapper
以将依赖项注入到您的控制器中。
如果您无法更改实例 api 那么您需要确保映射器在 运行 测试之前初始化
Mapper.Initialize(cfg => {
cgf.CreateMap<JournalViewModel, Journal>();
});
问题2
你的考试安排是
Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
Status = true
});
如您所知,这不适用于您使用实际实例调用 journalRepository.DeleteJournal
的情况。假设您使用的是 Telerik 的 JustMock,您应该安排一个更灵活的参数。
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(new OperationStatus
{
Status = true
});
来源:Handling Arguments in JustMock Arrangements
完成测试:实例API
[TestMethod]
public void Delete_Journal() {
// Arrange
//Configure mapping just for this test but something like this
//should be in accessible from your composition root and called here.
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
var mapper = config.CreateMapper(); // IMapper
// Simulate PDF file
var mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
var journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
var status = new OperationStatus {
Status = true
};
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);
var controller = new PublisherController(journalRepository, membershipRepository, mapper);
// Act
var result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}
完成测试:静态 API
[TestMethod]
public void Delete_Journal() {
// Arrange
//Configure mapping just for this test but something like this
//should be in accessible from your composition root and called here.
Mapper.Initialize(cfg => {
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
// Simulate PDF file
var mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
var journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
var status = new OperationStatus {
Status = true
};
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);
var controller = new PublisherController(journalRepository, membershipRepository);
// Act
var result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}
我正在为我的 ASP .NET MVC 控制器 class 创建一些单元测试,我 运行 遇到了一些非常严重的 运行ge 错误:
我的控制器代码如下:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal)
{
var selectedJournal = Mapper.Map<JournalViewModel, Journal>(journal);
var opStatus = _journalRepository.DeleteJournal(selectedJournal);
if (!opStatus.Status)
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
return RedirectToAction("Index");
}
我的测试代码如下:
[TestMethod]
public void Delete_Journal()
{
// Arrange
// Simulate PDF file
HttpPostedFileBase mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
JournalViewModel journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
Status = true
});
// Act
PublisherController controller = new PublisherController(journalRepository, membershipRepository);
RedirectToRouteResult result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}
问题 1 - 映射异常:
每次我 运行 我的测试都会收到以下异常:
Test Name: Delete_Journal Test
FullName: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
Test Source: \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs : line 132
Test Outcome: Failed Test Duration: 0:00:00,3822468Result StackTrace: at Journals.Web.Controllers.PublisherController.Delete(JournalViewModel journal) in \Source\Journals.Web\Controllers\PublisherController.cs:line 81 at Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() in \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:line 156 Result Message: Test method Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal threw exception: AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types: JournalViewModel -> Journal Journals.Model.JournalViewModel -> Journals.Model.Journal
Destination path: Journal
Source value: Journals.Model.JournalViewModel
classes JournalViewModel
和 Journal
之间似乎存在映射问题,但我不知道那是哪里。我将此代码添加到 Global.asax.cs
中的 Application_Start
:
Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();
并且从 Journal
映射到 JournalViewModel
正在工作。
最后我尝试添加 Mapper.CreateMap<JournalViewModel, Journal>();
作为 Delete
方法的第一行然后一切正常,但我不确定为什么。
问题 2 - HTML异常
一旦映射为 运行 上面的解决方法,我就会遇到一个问题,即 var opStatus = _journalRepository.DeleteJournal(selectedJournal);
中的 属性 Status
始终为 false,即使我使用了 Mock覆盖它并使其始终为真。这会导致抛出不应发生的 HTML 异常。
编辑
我在 Application_Start 中更改为:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
但我仍然有同样的错误。
编辑 - 问题 2 已解决
原来是我忘记将映射添加到我的单元测试中 class,所以我做了以下操作:
[TestInitialize]
public void TestSetup()
{
// Create necessary mappings
Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();
//...other code omitted for brevity
}
事实证明,这就是问题的根源。我认为由于 Global.asax.cs Application_Start() 从未在单元测试中被调用,所以映射从未被创建,所以我不得不在单元测试初始化中自己做这件事。
问题 1
Automapper 有两个 Static and Instance API。您应该考虑将实例 API 与 IMapper
一起使用并将其注入您的控制器。
public class PublisherController : Controller {
private readonly IMapper mapper;
public PublisherController(IJournalRepository journalRepository, IMembershipRepositry membershipRepository, IMapper mapper) {
//...other code omitted for brevity
this.mapper = mapper;
}
//...other code omitted for brevity
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal) {
var selectedJournal = mapper.Map<JournalViewModel, Journal>(journal);
var opStatus = _journalRepository.DeleteJournal(selectedJournal);
if (!opStatus.Status)
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
return RedirectToAction("Index");
}
}
这样可以根据需要更好地 mocking/faking/configuration 映射。您应该确保配置 IMapper
以将依赖项注入到您的控制器中。
如果您无法更改实例 api 那么您需要确保映射器在 运行 测试之前初始化
Mapper.Initialize(cfg => {
cgf.CreateMap<JournalViewModel, Journal>();
});
问题2
你的考试安排是
Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
Status = true
});
如您所知,这不适用于您使用实际实例调用 journalRepository.DeleteJournal
的情况。假设您使用的是 Telerik 的 JustMock,您应该安排一个更灵活的参数。
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(new OperationStatus
{
Status = true
});
来源:Handling Arguments in JustMock Arrangements
完成测试:实例API
[TestMethod]
public void Delete_Journal() {
// Arrange
//Configure mapping just for this test but something like this
//should be in accessible from your composition root and called here.
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
var mapper = config.CreateMapper(); // IMapper
// Simulate PDF file
var mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
var journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
var status = new OperationStatus {
Status = true
};
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);
var controller = new PublisherController(journalRepository, membershipRepository, mapper);
// Act
var result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}
完成测试:静态 API
[TestMethod]
public void Delete_Journal() {
// Arrange
//Configure mapping just for this test but something like this
//should be in accessible from your composition root and called here.
Mapper.Initialize(cfg => {
cfg.CreateMap<Journal, JournalViewModel>();
cfg.CreateMap<JournalViewModel, Journal>();
});
// Simulate PDF file
var mockFile = Mock.Create<HttpPostedFileBase>();
Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
Mock.Arrange(() => mockFile.ContentLength).Returns(255);
// Create view model to send.
var journalViewModel = new JournalViewModel();
journalViewModel.Id = 1;
journalViewModel.Title = "Test";
journalViewModel.Description = "TestDesc";
journalViewModel.FileName = "TestFilename.pdf";
journalViewModel.UserId = 1;
journalViewModel.File = mockFile; // Add simulated file
var status = new OperationStatus {
Status = true
};
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);
var controller = new PublisherController(journalRepository, membershipRepository);
// Act
var result = controller.Delete(journalViewModel) as RedirectToRouteResult;
// Assert
Assert.AreEqual(result.RouteValues["Action"], "Index");
}