kolhapuri
kolhapuri

Reputation: 1651

FluentAssertions Asserting multiple properties of a single object

Is there a way to do something like this using FluentAssertions

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

I am trying to avoid writing multiple Assert statements. This was possible with https://sharptestex.codeplex.com/ which I was using for the longest time. But SharpTestEx does not support .Net Core.

Upvotes: 44

Views: 30508

Answers (4)

Nick N.
Nick N.

Reputation: 13578

The .Match() solution does not return a good error message. So if you want to have a good error and only one assert then use:

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

Anonymous objects (use with care!)

If you want to only check certain members then use:

    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

Multiple asserts

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.

If you want this because you want multiple errors at once, consider wrapping your multi line assertions in an AssertionScope.

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

Above statement will now give both errors at once, if they both fail.

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

https://awesomeassertions.org/introduction#assertion-scopes

Upvotes: 53

Daan
Daan

Reputation: 2928

Assuming you use xUnit, you can just solve it by inheriting from the right base class. There is no need for an implementation change in your tests. Here is how this works:

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

Alternatively, you can just wrap your expectations into a ValueTuple. Here is how:

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

Upvotes: 2

Good Night Nerd Pride
Good Night Nerd Pride

Reputation: 8500

I use an extension function for this that works similarly to 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);
    }
}

Here is how I use it:

[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*")));

Unfortunately this doesn't generalize very well due to FluentAssertions' design, so you might have to provide multiple overloads of this method with different types in place of MyClass.

I think the truly correct way however is to implement an *Assertions type for the type you want to run such assertions against. The documentation provides 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);
    }
}

Upvotes: 1

Nkosi
Nkosi

Reputation: 247591

You should be able to use general purpose Match assertion to verify multiple properties of the subject via a predicate

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

Upvotes: 27

Related Questions