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

FluentAssertions Asserting multiple properties of a single object

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

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

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

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

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

.Match() 解决方案没有 return 好的错误消息。所以如果你想有一个好的错误并且 只有一个 断言然后使用:

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

匿名对象慎用!

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

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

Note: You will miss (new) members when testing like this. So only use if you really want to check only certain members now and in the future. Not using the exclude option will force you to edit your test when a new property is added and that can be a good thing

多个断言

Note: All given solutions gives you one line asserts. In my opinion there is nothing wrong with multiple lines of asserts as long as it is one assert functionally.

如果您想要这样做是因为您希望同时出现多个错误,请考虑将您的多行断言包装在 AssertionScope.

using (new AssertionScope())
{
    result.Property1.Should().Be("something");
    result.Property2.Should().Be("anotherthing");
}

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

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

我为此使用了一个扩展函数,其工作方式类似于 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 {0}{reason}, but found {1}.", 
                _ => filename, files => files.Select(file => file.Name));

        return new AndConstraint<DirectoryInfoAssertions>(this);
    }
}

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

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));
}