Compare object values in xUnit C# with Verify
The most common way to test the values of an object is to use assertions for each property to check exactly what the object contains.
This can be done for types, classes, enumerables. The more aspects of the object that are tested, the more certain you can be that everything works as expected.
Assertions can be made quickly with small classes. It gets really annoying when your class contains a class, which contains a class… and so on. Nesting creates a huge amount of validations.
The complexity can be solved by splitting the current class into minor classes to avoid manipulation, and by reducing the scope of each method, but this is not always possible, mainly when we want to validate an end-to-end object update workflow.
In this case, we want to be sure that the resulting object is exactly as expected, as in API response or data ingestion updates.
Let’s start with an assert validation of the following code. It creates a goat with a name and a color, then adds a habit.
public Task Test_Verify()
{
var goat = new Goat
{
Name = "Bicky",
Color = "Grey"
};
goat.AddHabit(".NET", 3); // First param is Name and second is Difficulty
Assert.Equal("Bicky", goat.Name);
Assert.Equal("Grey", goat.Color);
Assert.NotNull(goat.Habits);
Assert.Equal(1, goat.Habits.Count);
Assert.Equal(".NET", goat.Habits.First().Name);
Assert.Equal(3, goat.Habits.First().Difficulty);
}
The job is done, all properties are properly tested.
If you pay attention, something is not tested here! Have you noticed what it is?
Imagine that this class has been working fine for 2 years, the team of developers has changed (or you may still be on this project but you have to do other things in this period) and the Product Owner asks you to add the property Age
on the goats.
The first thing you do is update the Goat
class, add the implementation requirements, create tests to validate and push to the main branch.
There’s still something unpleasant here.
You remember our test above. It’s still green, when it shouldn’t be. We only tested the properties we knew about when the test was created, and the code won’t raise any red code since the assertions are still true.
Imagine exposing unwanted properties on your API without noticing it…
Verify to save us all
Presentation
Verify, a .NET library, helps us overcome this problem by generating a JSON file modeling the asserted object.
It works on all C# test libraries: xUnit, MSTest, NUnit and Expector for F#.
Each test using Verify will generate a file containing the test result. This file will be compared to the verified file of the test with the diff comparison.
If the two files are identical, the tested method returns the expected result. If not, something has changed and an error is raised. It is very simple!
The only difference is the first run because there is no file to compare with. The test will be red, and you will have to merge the received file into the verified file which is currently empty.
The repository contains an excellent diagram of how this works.
We will take a simple example to show how Verify works. It is possible to go much further by using the extensions :
- Ensure the completeness of an API response (code, content, headers and cookies)
- Validate the Moq configuration inside the test file to notice any changes of the initial state
- Track EF changes and SQL query creation
- Check the log flow
Quick example
The test has been updated to remove the assertions and replace them with the Verify()
function.
The goat
argument will be the serialized and compared result.
public Task Test_Verify()
{
var goat = new Goat
{
Name = "Bicky",
Color = "Grey"
};
goat.AddHabit(".NET", 3);
return Verify(goat);
}
Once the test launched, everything is red. 2 files are also creating, one for the verification and one containing the received object.
A pop-up should open to show the difference between the two files. As expected, the verified file is empty. Merge the contents to have the first verified file completed.
If you run the test again, it should be green.
Add extra settings
Verify also manages the parameters of Argon, the implemented serializer. In our case, we also want to display the properties that have a default value.
This step is important to make sure that we don’t have contained information not displayed by the serializer. There are many parameters, the link is in the comment.
// https://github.com/VerifyTests/Verify/blob/main/docs/serializer-settings.md
public VerifyUnitTests()
{
VerifierSettings
.AddExtraSettings(_ => _.DefaultValueHandling = DefaultValueHandling.Populate);
}
To continue the example, the Product Owner wants to add the age of the goat to update the habit difficulty calculation. This property must be contained in the class.
As expected, the Verify test raises an error. The Age
property has been added, so the two files are different.
We now have 2 choices: admit that the object must contain this property, or the method must return another class without the age.
Conclusion
Verify is a powerful library for simplifying the assertion of complex data models and documents such as API responses or log/PDF files.
Beware, Verify does not replace all property assertions. Personally, I see a huge use for API, logs and full object manipulation, but this is only a small part of my validation testing.
PS: All *.received.*
files must be excluded from source control if you want your git commits to stay clean.
Originally published at https://goatreview.com on November 18, 2022.