Reputation: 1651
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
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
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
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
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