【问题标题】:How do I verify mocked async method called with correct expression?如何验证使用正确表达式调用的模拟异步方法?
【发布时间】:2019-07-15 10:56:26
【问题描述】:

我已经使用 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);

现在我的测试通过了,但我真的想验证方法是否被正确调用,而不是被调用。我该如何以最简单/最好的方式解决这个问题?

【问题讨论】:

    标签: c# unit-testing moq xunit xunit.net


    【解决方案1】:

    您需要更具体地了解设置中使用的表达式。

    使用It.Is&lt;T&gt;() 并调用表达式进行设置和验证。

    [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 =&gt; x.Name == obj.Name)并验证它是否在给定正确参数的情况下被调用?我看到 lambda 编译表达式。但这不适用于任何给定的表达式吗?我明确想在设置中设置表达式本身并验证相同的表达式。我读到表达式不容易比较,因为它们是引用类型,并且您需要编写一些比较逻辑。这看起来要容易得多,但我认为这还不合适。
    猜你喜欢
    • 2014-06-08
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多