Test transformers

Hello rock stars!

I have a story to tell. Story of two transformers. Heroes who changed my outlook and perception of looking at the test code. Writing bunch of test cases was *not* boring anymore.

Premise

The biggest problem I see with a typical automated test code is that it is dumb and dry! It does not resolute. It does not connect with customers because it’s just pile of syntax and assertions while business folks are interested in semantics.

How about writing test cases focusing on business scenarios? How about using the same domain language, same vocabulary that business folks like to use, right in the test cases? How about treating test cases and associated code as the first class citizens? Suddenly appreciation level and interest in the test code increases. The tests start talking. This idea of writing the test cases using the business vocabulary and then use such test cases for quality control and go/no decision making is called acceptance testing, in my book.

Let’s see how to get it done. Goal of this write up is to introduce the tooling to get the acceptance tests done.

Breaking shackles

Optimums prime

Let me introduce you to Optimums Prime (Check out: specflow). Specflow is Cucumber for .NET. Some of you, especially folks with Java background might know about Cucumber. Specflow is all about describing the business rules, requirements in an English-like syntax that is extremely “readable”. The idea is that- business guys or PMs would describe a business feature which consists of multiple scenarios. Each scenario would be then described in a structured manner using easy syntax like Given, When and Then.

Here is a simple example to demonstrate the same. We all like to write validator classes that do some sort of validations. And then we write a bunch of test cases to make sure if the response is as expected. Here is a simple validator class that I would be using for the demonstration.

public class ClientRequestIdValidator
{
    private readonly string clientRequestId;

    public ClientRequestIdValidator(string         clientRequestId)
    {
        this.clientRequestId = clientRequestId;
    }

    public ValidatorResponse Validate()
    {
        var validatorResponse = new ValidatorResponse(string.Empty, string.Empty);

        if (string.IsNullOrEmpty(this.clientRequestId))
        {
            validatorResponse.ErrorCode = "1000";
            validatorResponse.ErrorMessage = "Client Request Id can not be empty or null value";
            return validatorResponse;
        }

        Guid clientRequestGuid;
        var isGuidParsingSuccessful = Guid.TryParse(this.clientRequestId, out clientRequestGuid);

        if (!isGuidParsingSuccessful)
        {
            validatorResponse.ErrorCode = "2000";
            validatorResponse.ErrorMessage = "Client Request Id must be a guid value";
            return validatorResponse;
        }

        if (clientRequestGuid.Equals(Guid.Empty))
        {
            validatorResponse.ErrorCode = "3000";
            validatorResponse.ErrorMessage = "Client Request Id can not be empty guid";
            return validatorResponse;
        }

        return validatorResponse;
    }
}

public class ValidatorResponse
{
    public ValidatorResponse(string errorCode, string errorMessage)
    {
        this.ErrorCode = errorCode;
        this.ErrorMessage = errorMessage;
    }
    public string ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
} 

As you can see, no rocket science going on here. Standard, simple C# code that validates a Guid value. Let’s see how we can write test cases for the same using SpecFlow. Ideally, I should be writing the test cases first! So sorry Uncle Bob! 🙂

In SpecFlow, like in any other test framework you put the world in a known state, change it by performing some action and then assert and check if the changed state matches the expected state. We use Given keyword to set the state, use When keyword to perform the action and Then keyword to assert.

Assuming you have SpecFlow for Visual Studio installed, let’s add a SocFlow Feature. Feature in SpecFlow is a top-level entity. A feature can have multiple scenarios.

image

This is how a feature file looks like:

As shown below using Given, the Client Request Id is initialized. Using When we are invoking validator which actually performs validation and using Then we are asserting if the response code and response message are as per expectation. Using keywords Scenario Outline and Examples we can set the table of values that are to be used in multiple cases.

Feature: Validate
	Make sure that API call validates the client request id

@mytag
Scenario Outline: Validate Client Request Id
	Given Client Request Id provided is <ClientRequestId>
	When Client Request Id validated
	Then Error Code should be <ErrorCode>
	And Error Message should be <ErrorMessage>

Examples: 

| TestCase               | ClientRequestId                      | ErrorCode | ErrorMessage                                     |
| EmptyClientRequestId   |                                      | 1000      | Client Request Id can not be empty or null value |
| EmptyGuid              | 00000000-0000-0000-0000-000000000000 | 3000      | Client Request Id can not be empty guid          |
| InvalidClientRequestId | Blah                                 | 2000      | Client Request Id must be a guid value           |
| ValidClientRequestId   | 76E6B8C8-7D26-4A83-B7C5-A052B82B6A21 |           |                                                  |

Once the feature file is set, just right click and use option Generate Step Definitions as shown below and then it generates the code-behind file for the feature file.

image

Make sure you have SpecFlow nuget package installed and correctly configured

This is how the feature code behind looks like:

[Binding]
public class ValidateClientRequestIdSteps
{
  [Given(@"Client Request Id provided is (.*)")]
  public void GivenClientRequestIdProvidedIs(string clientRequestId)
  {   
    ScenarioContext.Current["ClientRequestId"] = clientRequestId;
  }
        
   [When(@"Client Request Id validated")]
   public void WhenClientRequestIdValidated()
   {
    var validator = new ClientRequestIdValidator(
                            ScenarioContext.Current["ClientRequestId"].ToString());
    var result = validator.Validate();
    ScenarioContext.Current["ValidationResult"] = result;
   }
        
   [Then(@"Error Code should be (.*)")]
   public void ThenErrorCodeShouldBe(string errorCode)
   {
      var validationResult = ScenarioContext.Current["ValidationResult"] 
                                          as ValidatorResponse;
      validationResult.Should().NotBeNull();
      validationResult.ErrorCode.ShouldBeEquivalentTo(errorCode);
   }
        
   [Then(@"Error Message should be (.*)")]
   public void ThenErrorMessageShouldBe(string errorMessage)
   {
      var validationResult = ScenarioContext.Current["ValidationResult"] 
                                           as ValidatorResponse;
      validationResult.ErrorMessage.ShouldBeEquivalentTo(errorMessage);
    }
  }

Then you can bind the Specflow generated test cases with your favourite framework like NUnit, XUnit, MStest etc. from the configuration itself and use your favourite test runner to execute the test cases. In my case, I have below lines in my app.config

<specFlow>
  <unitTestProvider name="MsTest" />
</specFlow>

Bumblebee

The second hero I would like to introduce is Bumblebee (fluent assertions). Fluent assertions – as the name suggests is a nice and really intuitive way of writing assertions. With fluent assertions, the asserts look beautiful, natural and most importantly extremely readable. To add to the joy, when the test case fails, it tells exactly why it has failed with a nice descriptive message.

Make sure you pull the Fluent Assertions nuget package and to make it work.

image

As you can see in below snippet, I am asserting and checking if the actual response is null or empty. In this scenario – it would throw the exception if the response is not null or empty. It will also throw the exception if actual and expected are not equal. You can explore more and have fun with it.

var actual = "actual";
var expected = "expected";

//Message displayed:Expected string to be <null> or empty, 
actual.Should().BeNullOrEmpty();

//Message displayed:Expected subject to be "expected"
actual.ShouldBeEquivalentTo(expected); 

Happy testing!