【问题标题】:Unit Test Save Changes failing单元测试保存更改失败
【发布时间】:2017-04-21 09:58:43
【问题描述】:

框架

.NETCoreApp 1.1
EF Core 1.1.1
Xunit 2.2.0
Moq 4.7.8

Controller Post 方法
_yourRepository 被注入到控制器构造函数中,其类型为 IYourRepository

[HttpPost(Name = "CreateMethod")]
public async Task<IActionResult> CreateMethod([FromBody] ObjectForCreationDto objectDto)
{
    if (objectDto== null)
    {
        return BadRequest();
    }

    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    await _yourRespository.CreateObject(objectDto);

    if (!await _yourRespository.Save())
    {
        throw new Exception("Creating this object failed on save.");
    }            

    return Ok();
}

单元测试失败

[Fact]
public async Task CreateObject_WhenGoodDtoReceived_SuccessStatusReturned()
{
    // Arrange
    var mockRepo = new Mock<IYourRepository>();
    var controller = new YourController(mockRepo.Object);
    var objectForCreationDto = new ObjectForCreationDto { Code = "0001", Name = "Object One" };

    // Act
    var result = await controller.CreateObject(objectForCreationDto);

    // Assert
    Assert.IsType<OkObjectResult>(result);
}

测试失败是因为这条线

if (!await _yourRespository.Save())

总是评估为真。当它评估为 true 时,您可以看到代码抛出错误(由中间件处理)

_yourRepository.Save() 方法

public async Task<bool> Save()
{
    return (await _yourContext.SaveChangesAsync() >= 0);
}

我不确定如何解决问题,也不是 100% 确定它为什么会失败。

是因为模拟的IYourRepository 接口不包含Save 方法的实现吗?

如果是这样,这是否意味着要测试 Post 方法,我需要模拟我的 DbContext 并使用它构造我的 YourRepository 对象?

任何关于为什么失败以及如何纠正它的解释将不胜感激

【问题讨论】:

  • 代替await _yourRespository.Save() 试试_yourRespository.Save().Wait()
  • 他正在使用模拟,_yourRepository 没有功能。这就是模拟的全部意义,不是有一个具体的实现,而是“伪造”你希望它返回的结果

标签: c# asp.net-core moq entity-framework-core xunit2


【解决方案1】:

您需要设置存储库以从异步方法返回正确的任务。 Moq 允许使用 ReturnsAsync

[Fact]
public async Task CreateObject_WhenGoodDtoReceived_SuccessStatusReturned()
{
    // Arrange
    var mockRepo = new Mock<IYourRepository>();
    mockRepo.Setup(_ => _.Save()).ReturnsAsync(true);//<-- ADD THIS
    var controller = new YourController(mockRepo.Object);
    var objectForCreationDto = new ObjectForCreationDto { Code = "0001", Name = "Object One" };

    // Act
    var result = await controller.CreateObject(objectForCreationDto);

    // Assert
    Assert.IsType<OkObjectResult>(result);
}

【讨论】:

  • 感谢大家的快速回答。我仍在努力解决这个问题。如果我像这样测试 post 方法,这是否意味着我并没有真正检查它是否会起作用?我可以传入任何东西(假设它是正确的 Dto)并且测试会通过??还是测试保存行为应该保存(不是双关语!)用于集成测试?
  • 这只是测试控制器CreateMethod 方法的一个特定场景。我们模拟依赖项以单独运行被测方法。
  • @GreenyMcDuff:您正在测试您的控制器操作,而不是存储库。这就是您 mock 存储库以返回所需值的原因。例如,如果您想查看 Action 返回 OkObjectResult,您可以模拟 Save() 以返回 true。如果您想测试它是否抛出异常,您可以模拟 Save() 以返回 false 以模拟 db 错误,而无需使用 DB/DbContext 本身。这就是 mocks 和 unit 测试的全部意义(单元测试意味着:在不使用外部依赖的情况下测试一个单元(方法、类、属性)
  • 但是您的测试可能会关闭,因为在您的控制器操作中您正在进行 ModelState 验证 (ModelState.IsValid),我不确定这是否有效,除非您进行集成测试(使用 @ 987654328@ 类),因为在没有默认控制器激活器的情况下手动实例化 YourController 类时可能不会设置所需的服务
  • @Tseng 谢谢,这解释了一点。在ModelState.IsValid 点上同意你的观点。为了测试我刚刚在另一个测试中使用了controller.ModelState.AddModelError("Error", "Model error");
【解决方案2】:

new Mock&lt;IYourRepository&gt;(); 替换为new Mock&lt;IYourRepository&gt;(MockBehavior.Strict); - 现在当调用任何没有Setup 的方法时,它会抛出异常。

显然,您不应检查(断言)您未确定的内容(如返回值)。

【讨论】:

    【解决方案3】:

    Mock Save 方法默认返回false。您需要明确设置true 作为返回值:

    mockRepo.Setup(x => x.Save()).Returns(Task<bool>.FromResult(true));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多