最小起订量为什么模拟方法 return 为 null 并测试直到通过?
Moq why do mocked methods return null and tests till pass?
我正在尝试模拟下面的两个接口。
Mock<IEmailSender> emailSender = new Mock<IEmailSender>();
Mock<IEmailTemplate> emailTemplate = new Mock<IEmailTemplate>();
这是设置
emailTemplate.Setup(x => x.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>())).Returns(It.IsAny<string>());
emailSender.Setup(x => x.SendEmailAsync(It.IsAny<SendEmailViewModel>(), default)).ReturnsAsync(It.IsAny<SendEmailResultViewModel>());
这是调用的控制器操作。
[EnableCors(PolicyName = "AllowClientAccess")]
[HttpGet("Forgot")]
public async Task<IActionResult> ForgotPassword([FromQuery] string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
//MOQ file path not found
EmailTemplateViewModel model = new EmailTemplateViewModel();
model.Email = email;
model.RecipientName = user.UserName;
var message = _emailTemplate.GetForgotPasswordTemplate(model);
SendEmailViewModel sendEmailViewModel = new SendEmailViewModel();
sendEmailViewModel.RecipientName = user.UserName;
sendEmailViewModel.RecipientEmail = user.Email;
sendEmailViewModel.Subject = "ForgotPassword";
sendEmailViewModel.Body = message;
await _emailSender.SendEmailAsync(sendEmailViewModel);
return Ok(AddSuccess("Check your email", "Forgot Password"));
}
ModelState.AddModelError("Forgot Password","Unable to send email");
return BadRequest(ModelErrors());
}
这一行return为空
var message = _emailTemplate.GetForgotPasswordTemplate(model);
这里是方法代码
public string GetForgotPasswordTemplate(EmailTemplateViewModel model)
{
try
{
var utcNow = DateTime.Now;
if (_testEmailTemplate == null)
if (File.Exists("Helpers/Templates/ForgotPasswordEmail.template"))
_testEmailTemplate = ReadPhysicalFile("Helpers/Templates/ForgotPasswordEmail.template");
var appUrl = _configuration.GetSection("ApplicationUrl").Value +
"/reset-password?&email=" + model.Email;
var emailMessage = _testEmailTemplate
.Replace("{user}", model.RecipientName)
.Replace("{testDate}", utcNow.ToString(CultureInfo.InvariantCulture))
.Replace("{appUrl}", appUrl);
return emailMessage;
}
catch (Exception e)
{
Log.Warning(e, "Email error");
throw;
}
}
这一行也return为空
await _emailSender.SendEmailAsync(sendEmailViewModel);
这里是方法代码
public Task<SendEmailResultViewModel> SendEmailAsync(SendEmailViewModel model, SmtpConfig config = default)
{
model.IsHtml = true;
var from = new MailboxAddress(_config.FromName, _config.FromEmail);
var to = new MailboxAddress(model.RecipientName, model.RecipientEmail);
return SendEmailAsync(@from, new[] {to}, model.Body, model.Body, config, model.IsHtml);
}
这是测试
[Theory]
[InlineData("stephen@kaizenappz.com")]
public async Task WhenAUserForgetsPasswordAHttpStatusCode200ShouldBeReturnedAsync(string email)
{
var confirmUser = await Controller.ForgotPassword(email);
var result = confirmUser as OkObjectResult;
var actual = (HttpStatusCode)result?.StatusCode.Value;
var expected = HttpStatusCode.OK;
Assert.AreEqual(expected, actual);
}
但是测试通过了,我想知道的是为什么这两种方法 return 都为空,为什么测试通过,即使它 return 为空。我怎样才能得到这些 return 东西?
我不明白的一件事是什么时候使用 It.Any 并且只是传入一个带有一些测试数据的普通对象。如果我使用 It.Any 并且我需要将模型传递到我的控制器操作中,我应该如何检查用户是否存在?
设置阶段
每当您需要模拟接口方法时,请尝试在设置过程中保持宽容。
换句话说允许接收任何参数:
const string mockedForgotPwdTemplate = "...";
emailTemplate
.Setup(template => template.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>()))
.Returns(mockedForgotPwdTemplate);
如果你的return值取决于参数
然后使用 Returns
的重载,它接受一个函数:
const string mockedTemplateWithSubject = "...";
const string mockedTemplateWithoutSubject = "...";
emailTemplate
.Setup(template => template.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>()))
.Returns((EmailTemplateViewModel vm) => !string.IsNullOrEmpty(vm.Subject) ? mockedTemplateWithSubject : mockedTemplateWithoutSubject);
验证阶段
在断言期间尽量具体。
如果您有权访问该参数,则将其传递给 Verify
:
var mockedViewTemplate = new EmailTemplateViewModel { ... };
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(mockedViewTemplate), Times.Once);
请记住,moq 使用引用检查来确定 expected
和 actual
参数是否相同。如果您没有对此参数的引用,那么您应该使用 It.Is<T>
:
const string expectedSubject = "ForgotPassword";
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(
It.Is<EmailTemplateViewModel>(vm => expectedSubject == vm.Subject), Times.Once);
或者如果您希望对多个属性进行断言,则:
private bool AssertViewModel(EmailTemplateViewModel actualVM, string expectedSubject, string expectedRecipientName)
{
Assert.Equal(expectedSubject, actualVM.Subject);
Assert.Equal(expectedRecipientName, actualVM.RecipientName);
return true;
}
//...
const string expectedSubject = "ForgotPassword", expectedRecipent = "...";
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(
It.Is<EmailTemplateViewModel>(vm => this.AssertViewModel(vm, expectedSubject, expectedRecipient)), Times.Once);
我正在尝试模拟下面的两个接口。
Mock<IEmailSender> emailSender = new Mock<IEmailSender>();
Mock<IEmailTemplate> emailTemplate = new Mock<IEmailTemplate>();
这是设置
emailTemplate.Setup(x => x.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>())).Returns(It.IsAny<string>());
emailSender.Setup(x => x.SendEmailAsync(It.IsAny<SendEmailViewModel>(), default)).ReturnsAsync(It.IsAny<SendEmailResultViewModel>());
这是调用的控制器操作。
[EnableCors(PolicyName = "AllowClientAccess")]
[HttpGet("Forgot")]
public async Task<IActionResult> ForgotPassword([FromQuery] string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
//MOQ file path not found
EmailTemplateViewModel model = new EmailTemplateViewModel();
model.Email = email;
model.RecipientName = user.UserName;
var message = _emailTemplate.GetForgotPasswordTemplate(model);
SendEmailViewModel sendEmailViewModel = new SendEmailViewModel();
sendEmailViewModel.RecipientName = user.UserName;
sendEmailViewModel.RecipientEmail = user.Email;
sendEmailViewModel.Subject = "ForgotPassword";
sendEmailViewModel.Body = message;
await _emailSender.SendEmailAsync(sendEmailViewModel);
return Ok(AddSuccess("Check your email", "Forgot Password"));
}
ModelState.AddModelError("Forgot Password","Unable to send email");
return BadRequest(ModelErrors());
}
这一行return为空
var message = _emailTemplate.GetForgotPasswordTemplate(model);
这里是方法代码
public string GetForgotPasswordTemplate(EmailTemplateViewModel model)
{
try
{
var utcNow = DateTime.Now;
if (_testEmailTemplate == null)
if (File.Exists("Helpers/Templates/ForgotPasswordEmail.template"))
_testEmailTemplate = ReadPhysicalFile("Helpers/Templates/ForgotPasswordEmail.template");
var appUrl = _configuration.GetSection("ApplicationUrl").Value +
"/reset-password?&email=" + model.Email;
var emailMessage = _testEmailTemplate
.Replace("{user}", model.RecipientName)
.Replace("{testDate}", utcNow.ToString(CultureInfo.InvariantCulture))
.Replace("{appUrl}", appUrl);
return emailMessage;
}
catch (Exception e)
{
Log.Warning(e, "Email error");
throw;
}
}
这一行也return为空
await _emailSender.SendEmailAsync(sendEmailViewModel);
这里是方法代码
public Task<SendEmailResultViewModel> SendEmailAsync(SendEmailViewModel model, SmtpConfig config = default)
{
model.IsHtml = true;
var from = new MailboxAddress(_config.FromName, _config.FromEmail);
var to = new MailboxAddress(model.RecipientName, model.RecipientEmail);
return SendEmailAsync(@from, new[] {to}, model.Body, model.Body, config, model.IsHtml);
}
这是测试
[Theory]
[InlineData("stephen@kaizenappz.com")]
public async Task WhenAUserForgetsPasswordAHttpStatusCode200ShouldBeReturnedAsync(string email)
{
var confirmUser = await Controller.ForgotPassword(email);
var result = confirmUser as OkObjectResult;
var actual = (HttpStatusCode)result?.StatusCode.Value;
var expected = HttpStatusCode.OK;
Assert.AreEqual(expected, actual);
}
但是测试通过了,我想知道的是为什么这两种方法 return 都为空,为什么测试通过,即使它 return 为空。我怎样才能得到这些 return 东西?
我不明白的一件事是什么时候使用 It.Any 并且只是传入一个带有一些测试数据的普通对象。如果我使用 It.Any 并且我需要将模型传递到我的控制器操作中,我应该如何检查用户是否存在?
设置阶段
每当您需要模拟接口方法时,请尝试在设置过程中保持宽容。
换句话说允许接收任何参数:
const string mockedForgotPwdTemplate = "...";
emailTemplate
.Setup(template => template.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>()))
.Returns(mockedForgotPwdTemplate);
如果你的return值取决于参数
然后使用 Returns
的重载,它接受一个函数:
const string mockedTemplateWithSubject = "...";
const string mockedTemplateWithoutSubject = "...";
emailTemplate
.Setup(template => template.GetForgotPasswordTemplate(It.IsAny<EmailTemplateViewModel>()))
.Returns((EmailTemplateViewModel vm) => !string.IsNullOrEmpty(vm.Subject) ? mockedTemplateWithSubject : mockedTemplateWithoutSubject);
验证阶段
在断言期间尽量具体。
如果您有权访问该参数,则将其传递给 Verify
:
var mockedViewTemplate = new EmailTemplateViewModel { ... };
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(mockedViewTemplate), Times.Once);
请记住,moq 使用引用检查来确定 expected
和 actual
参数是否相同。如果您没有对此参数的引用,那么您应该使用 It.Is<T>
:
const string expectedSubject = "ForgotPassword";
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(
It.Is<EmailTemplateViewModel>(vm => expectedSubject == vm.Subject), Times.Once);
或者如果您希望对多个属性进行断言,则:
private bool AssertViewModel(EmailTemplateViewModel actualVM, string expectedSubject, string expectedRecipientName)
{
Assert.Equal(expectedSubject, actualVM.Subject);
Assert.Equal(expectedRecipientName, actualVM.RecipientName);
return true;
}
//...
const string expectedSubject = "ForgotPassword", expectedRecipent = "...";
emailTemplate
.Verify(template => template.GetForgotPasswordTemplate(
It.Is<EmailTemplateViewModel>(vm => this.AssertViewModel(vm, expectedSubject, expectedRecipient)), Times.Once);