如何验证模拟的异步方法被正确调用?

12
我使用XUnit和Moq编写了一些测试。模拟接口实现的一种方法是接受类型为Expression<Func<T, bool>>的参数。似乎一切正常,但我不理解验证方法是否调用了正确的表达式的工作原理。
给定以下测试,尽管调用看起来正确,但该方法未返回设置的值:
    /// <summary>
    /// Verify that a create fails appropriately when another entity was found given the same name,
    /// Verify that the message of the exception contains the duplicate name
    /// Verify that update fails before other calls are made
    /// </summary>
    [Theory(DisplayName = "Definition Types service - Create")]
    [MemberData(nameof(DefinitionTypesTestData.SingleDefinitionType), MemberType = typeof(DefinitionTypesTestData))]
    public async Task CreateDefinitionTypeShouldThrowDuplicateTest(DefinitionType obj)
    {
        if (obj == null) throw new NullReferenceException($"Test data was null in theory 'Definition Types service - Create'");
        var crudService = new Mock<IEntityCrudService<DefinitionType>>();
        crudService.Setup(crud => crud.GetEntitiesAsync(x => x.Name == obj.Name))
            .Returns(Task.FromResult<IEnumerable<DefinitionType>>(new List<DefinitionType> {
            new DefinitionType {
                Name = "Test",
                Description = "Test",
                DisplayName = "Test",
                ID = Guid.NewGuid()
            } }));
        IDefinitionTypesService serviceUnderTest = new DefinitionTypesService(crudService.Object);
        var exception = await Assert.ThrowsAsync<EntityDuplicationException>(() => serviceUnderTest.InsertDefinitionTypeAsync(obj));
        Assert.Contains("Definition type", exception.DisplayMessage);
        Assert.Contains(obj.Name, exception.DisplayMessage);
        crudService.Verify(crud => crud.GetEntitiesAsync(x => x.Name == obj.Name), Times.Once);
        crudService.VerifyNoOtherCalls();
    }

我有一个InsertDefinitionType(DefinitionType obj)的实现:
async Task IDefinitionTypesService.InsertDefinitionTypeAsync(DefinitionType obj)
    {
        var definitiontypes = await _definitionTypeService.GetEntitiesAsync(x => x.Name == obj.Name);

        if(definitiontypes.Any())
        {
            throw new EntityDuplicationException("Definition type", name: obj.Name);
        }

        try
        {
            await _definitionTypeService.CreateAsync(obj);
        }
        catch (EntityNotSavedException exc)
        {
            exc.EntityType = "Definition type";
            throw exc;
        }
    }

当我按照以下方式更改设置时,确实得到了结果,但在我的验证功能中,它说该函数从未被调用(或者至少使用给定表达式没有被调用)。:
crudService.Setup(crud => crud.GetEntitiesAsync(It.IsAny<Expression<Func<DefinitionType, bool>>>()))
    .Returns(Task.FromResult<IEnumerable<DefinitionType>>(new List<DefinitionType> {
        new DefinitionType {
            Name = "Test",
            Description = "Test",
            DisplayName = "Test",
            ID = Guid.NewGuid()
        } }));

现在我还将验证更改为更通用的方式:

crudService.Verify(crud => crud.GetEntitiesAsync(It.IsAny<Expression<Func<DefinitionType, bool>>>()), Times.Once);

现在我的测试通过了,但我真的想验证方法被正确调用而不是根本没有被调用。有什么最简单/最好的方法解决这个问题呢?

1个回答

4

关于设置中使用的表达式,您需要更加具体。

在设置和验证中使用It.Is<T>()并调用表达式。

[Theory(DisplayName = "Definition Types service - Create")]
[MemberData(nameof(DefinitionTypesTestData.SingleDefinitionType), MemberType = typeof(DefinitionTypesTestData))]
public async Task CreateDefinitionTypeShouldThrowDuplicateTest(DefinitionType obj) {

    if (obj == null) throw new NullReferenceException($"Test data was null in theory 'Definition Types service - Create'");

    //Arrange
    var crudService = new Mock<IEntityCrudService<DefinitionType>>();

    var list = new List<DefinitionType>() {
        new DefinitionType {
            Name = "Test",
            Description = "Test",
            DisplayName = "Test",
            ID = Guid.NewGuid()
        }
    };

    crudService
        .Setup(_ => _.GetEntitiesAsync(It.Is<Expression<Func<DefinitionType, bool>>>(exp => exp.Compile()(obj))))
        .ReturnsAsync(list);

    IDefinitionTypesService serviceUnderTest = new DefinitionTypesService(crudService.Object);

    //Act
    var exception = await Assert.ThrowsAsync<EntityDuplicationException>(() => serviceUnderTest.InsertDefinitionTypeAsync(obj));

    //Assert
    Assert.Contains("Definition type", exception.DisplayMessage);
    Assert.Contains(obj.Name, exception.DisplayMessage);
    crudService.Verify(_ => _.GetEntitiesAsync(It.Is<Expression<Func<DefinitionType, bool>>>(exp => exp.Compile()(obj))), Times.Once);
    crudService.VerifyNoOtherCalls();
}

特别注意在设置和验证中使用的表达方式。

_ => _.GetEntitiesAsync(It.Is<Expression<Func<DefinitionType, bool>>>(exp => exp.Compile()(obj)))

嗨Nkosi,谢谢你的回答。我不太明白,如果我是个新手,对不起。我如何指定表达式本身(即x => x.Name == obj.Name),并验证在给定正确参数的情况下是否被调用?我看到lambda编译表达式。但这难道不适用于任何给定的表达式吗?我明确想在设置和验证中设置相同的表达式。我读到表达式不容易比较,因为它们是引用类型,需要编写一些逻辑进行比较。这看起来更容易,但我还没有看到这种适合性。 - Bas Kooistra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接