Enhancing Your Unit Tests with Pressius

As developers, we need to write unit tests regularly. Its to insure our code against changes in the future as a living document of the present. The value of unit tests is often cannot be measured in the present but more realised when the unexpected happens in the future.

When we write unit tests, we often require to create mock objects. The mock objects acted as a replacement of our model. Here’s an oversimplified example of a mock customer object that we may need to create. It only has 4 attributes. In reality, we may have more than 40 or even 50 attributes.

var customer = new Customer()
{
    Id = 1,
    FirstName = “Bruce”,
    LastName = “Wayne”,
    Occupation = “Entrepreneur”
};

The customer is a valid customer. As a good unit tester, we need to tests all permutations of valid values for all of the attributes. Can the attribute FirstName and LastName accept non-alphabetical values? What are the valid characters for FirstName and LastName? What are the expected outputs? If the occupation is an enum but it saves into the database as a string, how will our application behave given other values?

The way we would tackle the scenarios mentioned above are usually by creating a series of customer object and maybe creating a test suite for a set of scenarios. With xUnit framework, below is a small example of what we normally would create. The sample below indicates what values are accepted as valid values for the application. It includes null, non-alphabetical characters for the first name and last name, and some values specific for the occupation.

[Theory]
[InlineData(1312, “James”, “Warden”, “Cashier”)]
[InlineData(998, “12345”, “W4’[]2”, “Tree Cutter”)]
[InlineData(9999, “James”, “The 3rd”, “Royalty”)]
[InlineData(1231, null, null, null)]
public void Should_Create_Valid_Customer(int id, string firstname, string lastname, string occupation)
{
    //Prepare for test
    var customer = new Customer()
    {
        Id = id,
        FirstName = firstname,
        LastName = lastname,
        Occupation = occupation
    };
    var result = _service.CreateCustomer(customer );
    result.Id.ShouldBeGreaterThan(0);
    result.FirstName.ShouldBe(firstname);
}

Whilst the sample above is a valid test case, it is, however, is not an exhaustive list of the permutations. The non-alphabetical input for first name and last name is being tested at the same test. All of the null values are tested within the same test. The test does not cater valid scenarios where the first name and last name contains alphabet, but the occupation may be null, or where the last name may be null, but the first name and occupation contains values. There are many more permutations that the test above does not cater for.

In addition to the drawback above, the sample values of what makes a “Valid Customer” is lost and cannot be repeated in other unit tests that may require valid customer object. For example, suppose there is a function where a valid customer creates an order. The order creation may take an input of a valid customer object. As the values of the attributes are not being saved anywhere, creating permutations of another valid customer with the same attribute values is not possible (it will involve a lot of copy pasting). How would the create order behave if the supposedly valid customer object does not have a first name or last name?

The intricacies of managing list permutations of object models in our unit tests are in itself an art. A good design to create useful, robust, valid and evolvable unit tests is necessary.  

Introducing Pressius

Pressius is an open source NuGet library that helps to create a permutation of objects. It generates a list of the targeted object by permutating either from the attributes or from the constructor. Let’s look into how we would tackle the above concern with Pressius.

The simplest way to create a permutation with Pressius is just by calling it.

var customerList = Permutor.Generate();

By default, Pressius used its default permutation value list. Those default values can be found in https://github.com/LeonSutedja/Pressius. The command above will result in the following attribute permutations of customer object:

10 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog 1234567890 Cozy lummox gives smart squid who asks for job pen
10 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog ~!@#$%&*()_+=-`\][{}|;:,./?><'" The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10  The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10 xxx... The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
-2147483648 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
2147483647 The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog

The default permutation ensures several scenarios for string and integer attribute type. To further refine a valid customer object, we need to provide custom attribute values that define valid customer. Let’s define a set of valid values for the first name attribute.

public class ValidFirstName : DefaultParameterDefinition
{
    public override List InputCatalogues =>
        new List {
           "John",
           "Anastasia",
            ""
        };

    public override ParameterTypeDefinition TypeName =>
        new ParameterTypeDefinition("FirstName");

    public override bool CompareParamName => true;
}

Let’s breakdown ValidFirstName class. InputCatalogues contain the list of values that we consider valid for the first name. By default, Pressius is able to attach to a particular type (e.g int, string, and others). In this case, we would like to create valid values for the attribute name. The target attribute name is “FirstName”. To enable comparing against attribute name, we set the CompareParamName to true.

Next, we change the calling method to the following:

var permutor = new Permutor();
var pressiusTestObjectList = permutor
    .AddParameterDefinition(new ValidFirstName())
    .GeneratePermutation();

The result will be the customer object with the following attributes:

10 John The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10 John The quick brown fox jumps over the lazy dog 1234567890 Cozy lummox gives smart squid who asks for job pen
10 John The quick brown fox jumps over the lazy dog
10 John The quick brown fox jumps over the lazy dog ~!@#$%&*()_+=-`\][{}|;:,./?><'" The quick brown fox jumps over the lazy dog
10 John  The quick brown fox jumps over the lazy dog
10 John xxx... The quick brown fox jumps over the lazy dog
10 Anastasia The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
10  The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
-2147483648 John The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
2147483647 John The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog

It looks better already. Let’s finish this up by setting the valid values for last name and occupation.

public class ValidLastName : DefaultParameterDefinition
{
    public override List InputCatalogues =>
        new List {
           "Wick",
           "Laluna",
            ""
        };

    public override ParameterTypeDefinition TypeName =>
        new ParameterTypeDefinition("LastName");

    public override bool CompareParamName => true;
}

public class ValidOccupation : DefaultParameterDefinition
{
    public override List InputCatalogues =>
        new List {
           "Entrepreneur",
           "Car Dealer",
           "Death Maker"
        };

    public override ParameterTypeDefinition TypeName =>
        new ParameterTypeDefinition("Occupation");

    public override bool CompareParamName => true;
}

To add more to our model, we need to ensure that the id field is specified as an id. The final result is as follows:

var permutor = new Permutor();
var pressiusTestObjectList = permutor
    .AddParameterDefinition(new ValidFirstName())
    .AddParameterDefinition(new ValidLastName())
    .AddParameterDefinition(new ValidOccupation())
    .WithId("Id")
    .GeneratePermutation();

The .WithId input specifies which attribute name is an id. The output is as below:

1 John Wick Entrepreneur
2 John Wick Car Dealer
3 John Wick Death Maker
4 John Laluna Entrepreneur
5 John  Entrepreneur
6 Anastasia Wick Entrepreneur
7  Wick Entrepreneur
8 John Wick Entrepreneur
9 John Wick Entrepreneur

By creating a set of valid values using Pressius, we can ensure that the customer instances are repeatable across our test suites. We can reuse the same customer class in other places. If the rule for valid customer changes, such as a new occupation is being added or the first name attribute can now accept the null value, we can simply update the attribute definition classes.

Pressius is a tool that can help to create consistency in our unit test suites. It helps by simplifying object model permutation. More information can be found in the https://github.com/LeonSutedja/Pressius. The NuGet package can be found in https://www.nuget.org/packages/Pressius.  

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s