FluentAssertions Утверждение нескольких свойств одного объекта

Есть ли способ сделать что-то подобное с помощью FluentAssertions?

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

Я пытаюсь избежать написания нескольких операторов Assert. Это было возможно с https://sharptestex.codeplex.com/, которым я пользовался дольше всего. Но SharpTestEx не поддерживает .Net Core.


person kolhapuri    schedule 21.04.2017    source источник
comment
Что это должно делать?   -  person Dennis Doomen    schedule 29.05.2017
comment
На момент написания принятый ответ, возможно, был лучшим, а не больше...   -  person OnTheFly    schedule 03.09.2019


Ответы (4)


Вы должны иметь возможность использовать утверждение Match общего назначения для проверки нескольких свойств субъекта с помощью предиката.

response.Should()
        .Match<MyResponseObject>((x) => 
            x.Property1 == "something" && 
            x.Property2 == "anotherthing"
        );
person Nkosi    schedule 29.05.2017
comment
Хотя этот код работает, сообщение об ошибке при сбое утверждения очень неудобно. Слишком далеко от того, что обычно выдает FluentAssertions. Вместо этого я бы предложил использовать несколько утверждений :) - person the_joric; 03.11.2017
comment
@the_joric или используйте ShouldBeEquivalentTo - person Nick N.; 04.10.2018

Решение .Match() не возвращает правильное сообщение об ошибке. Поэтому, если вы хотите иметь хорошую ошибку и только одно утверждение, используйте:

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

Анонимные объекты (используйте осторожно!)

Если вы хотите проверить только определенных членов, используйте:

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

Примечание. При таком тестировании вы пропустите (новых) участников. Так что используйте только в том случае, если вы действительно хотите проверить только определенных участников сейчас и в будущем. Неиспользование параметра исключения заставит вас редактировать тест при добавлении нового свойства, и это может быть хорошо.

Несколько утверждений

Примечание. Все приведенные решения дают вам одну строку утверждений. На мой взгляд, нет ничего плохого в нескольких строках утверждений, если функционально это одно утверждение.

Если вы хотите этого, потому что вам нужно несколько ошибок одновременно, подумайте о том, чтобы обернуть свои многострочные утверждения в файл AssertionScope.

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

Приведенный выше оператор теперь будет выдавать обе ошибки сразу, если они обе не работают.

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

person Nick N.    schedule 04.10.2018
comment
Не могли бы вы поподробнее рассказать об этом Анонимные объекты (используйте с осторожностью!)? - person Menyus; 27.03.2020
comment
@Menyus Да, при таком тестировании вы пропустите (новых) участников. Поэтому используйте его только в том случае, если вы действительно хотите проверять только определенных участников сейчас и в будущем. Неиспользование параметра исключения заставит вас редактировать тест при добавлении нового свойства, и это может быть хорошо. - person Nick N.; 30.03.2020

Я использую для этого функцию расширения, которая работает аналогично 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 для типа, против которого вы хотите запускать такие утверждения. В документации приведен пример:

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);
    }
}
person Good Night Nerd Pride    schedule 30.10.2020

Предполагая, что вы используете 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));
}
person Daan    schedule 04.11.2020