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

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

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

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

【问题讨论】:

  • 这应该怎么做?
  • 在撰写本文时,公认的答案可能是最好的,不再是......

标签: c# .net-core fluent-assertions


【解决方案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 {0}{reason}, 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));
    }
    

    【讨论】:

      猜你喜欢
      • 2023-02-07
      • 1970-01-01
      • 2016-05-12
      • 2022-01-17
      • 2022-05-26
      • 2014-04-08
      • 1970-01-01
      • 2019-07-09
      • 1970-01-01
      相关资源
      最近更新 更多