FluentAssertions 断言单个对象的多个属性

Posted

技术标签:

【中文标题】FluentAssertions 断言单个对象的多个属性【英文标题】:FluentAssertions Asserting multiple properties of a single object 【发布时间】:2017-09-18 13:02:06 【问题描述】:

有没有办法使用 FluentAssertions 来做这样的事情

response.Satisfy(r =>
    r.Property1== "something" &&
    r.Property2== "anotherthing"));

我试图避免编写多个 Assert 语句。我使用时间最长的https://sharptestex.codeplex.com/ 可以做到这一点。但是 SharpTestEx 不支持 .Net Core。

【问题讨论】:

这应该怎么做? 在撰写本文时,公认的答案可能是最好的,不再是...... 【参考方案1】:

您应该能够使用通用的Match 断言通过谓词验证主题的多个属性

response.Should()
        .Match<MyResponseObject>((x) => 
            x.Property1 == "something" && 
            x.Property2 == "anotherthing"
        );

【讨论】:

虽然此代码有效,但断言失败时的错误消息非常尴尬。与 FluentAssertions 通常产生的结果相去甚远。我建议改用多个断言:) @the_joric 或使用ShouldBeEquivalentTo【参考方案2】:

.Match() 解决方案没有返回好的错误消息。因此,如果您想要一个好的错误并且只有一个断言,那么请使用:

result.Should().BeEquivalentTo(new MyResponseObject()
            
                Property1 = "something",
                Property2 = "anotherthing"
            );

匿名对象谨慎使用!

如果您只想检查某些成员,请使用:

    result.Should().BeEquivalentTo(new
            
                Property1 = "something",
                Property2 = "anotherthing"
            , options => options.ExcludingMissingMembers());

注意:在进行这样的测试时,您会错过(新)成员。所以只有当你使用 现在和将来真的只想检查某些成员。不是 使用 exclude 选项将强制您在新的测试 添加了属性,这可能是一件好事

多个断言

注意所有给定的解决方案都会给你一行断言。在我看来,多行断言没有错,只要 因为它在功能上是一个断言。

如果您想要这样做是因为您想要一次出现多个错误,请考虑将您的多行断言包装在 AssertionScope 中。

using (new AssertionScope())

    result.Property1.Should().Be("something");
    result.Property2.Should().Be("anotherthing");

上面的语句现在会同时给出两个错误,如果它们都失败了。

https://fluentassertions.com/introduction#assertion-scopes

【讨论】:

你能详细说明这个“匿名对象(小心使用!)”吗? @Menyus 是的,在进行这样的测试时,您会错过(新)成员。因此,只有在您现在和将来真的想只检查某些成员时才使用。不使用 exclude 选项将迫使您在添加新属性时编辑测试,这可能是一件好事。【参考方案3】:

我为此使用了一个与SatisfyRespectively() 类似的扩展函数:

public static class FluentAssertionsExt 
    public static AndConstraint<ObjectAssertions> Satisfy(
        this ObjectAssertions parent,
        Action<MyClass> inspector) 
        inspector((MyClass)parent.Subject);
        return new AndConstraint<ObjectAssertions>(parent);
    

这是我的使用方法:

[TestMethod] public void FindsMethodGeneratedForLambda() =>
    Method(x => x.Lambda())
    .CollectGeneratedMethods(visited: empty)
    .Should().ContainSingle().Which
        .Should().Satisfy(m => m.Name.Should().Match("<Lambda>*"))
        .And.Satisfy(m => m.DeclaringType.Name.Should().Be("<>c"));

[TestMethod] public void FindsMethodGeneratedForClosure() =>
    Method(x => x.Closure(0))
    .CollectGeneratedMethods(visited: empty)
    .Should().HaveCount(2).And.SatisfyRespectively(
        fst => fst.Should()
            .Satisfy(m => m.Name.Should().Be(".ctor"))
            .And.Satisfy(m => m.DeclaringType.Name.Should().Match("<>c__DisplayClass*")),
        snd => snd.Should()
            .Satisfy(m => m.Name.Should().Match("<Closure>*"))
            .And.Satisfy(m => m.DeclaringType.Name.Should().Match("<>c__DisplayClass*")));

不幸的是,由于 FluentAssertions 的设计,这并不能很好地概括,因此您可能必须提供具有不同类型的此方法的多个重载来代替 MyClass

我认为真正正确的方法是为您要运行此类断言的类型实现 *Assertions 类型。文档提供an example:

public static class DirectoryInfoExtensions 

    public static DirectoryInfoAssertions Should(this DirectoryInfo instance)
    
      return new DirectoryInfoAssertions(instance); 
     


public class DirectoryInfoAssertions : 
    ReferenceTypeAssertions<DirectoryInfo, DirectoryInfoAssertions>

    public DirectoryInfoAssertions(DirectoryInfo instance)
    
        Subject = instance;
    

    protected override string Identifier => "directory";

    public AndConstraint<DirectoryInfoAssertions> ContainFile(
        string filename, string because = "", params object[] becauseArgs)
    
        Execute.Assertion
            .BecauseOf(because, becauseArgs)
            .ForCondition(!string.IsNullOrEmpty(filename))
            .FailWith("You can't assert a file exist if you don't pass a proper name")
            .Then
            .Given(() => Subject.GetFiles())
            .ForCondition(files => files.Any(fileInfo => fileInfo.Name.Equals(filename)))
            .FailWith("Expected context:directory to contain 0reason, but found 1.", 
                _ => filename, files => files.Select(file => file.Name));

        return new AndConstraint<DirectoryInfoAssertions>(this);
    

【讨论】:

【参考方案4】:

假设您使用 xUnit,您可以通过从正确的基类继承来解决它。在您的测试中无需更改实现。以下是它的工作原理:

public class UnitTest1 : TestBase

    [Fact]
    public void Test1()
    
        string x = "A";
        string y = "B";
        string expectedX = "a";
        string expectedY = "b";
        x.Should().Be(expectedX);
        y.Should().Be(expectedY);
    


public class TestBase : IDisposable

    private AssertionScope scope;
    public TestBase()
    
        scope = new AssertionScope();
    

    public void Dispose()
    
        scope.Dispose();
    

或者,您可以将您的期望包装到一个 ValueTuple 中。方法如下:

[Fact]
public void Test2()

    string x = "A";
    string y = "B";
    string expectedX = "a";
    string expectedY = "b";
    (x, y).Should().Be((expectedX, expectedY));

【讨论】:

以上是关于FluentAssertions 断言单个对象的多个属性的主要内容,如果未能解决你的问题,请参考以下文章

流利的断言;结合集合和对象图比较断言

C# FluentAssertions 在断言失败后继续

[C#FluentAssertions在断言失败后继续

如何使用 FluentAssertions 4.x 版断言异常?

如何在 FluentAssertions 中使用方法

FluentAssertions Should().BeOfType() 还是派生类型?