3 Rules for TDD

I couldn’t help but share this. There couldn’t be a better way to summarize the unwritten rules of TDD, than the one wrote by Uncle Bob in his famous book ‘Agile Principles, Patterns and Practices in C#”.

  • Don’t write any production code until you have written a failing unit test.
  • Don’t write more of a unit test than is sufficient to fail or fail to compile.
  • Don’t write any more production code than is sufficient to pass the failing test.

What it emphasises is when to switch between writing Unit Test and Production code, a key point, atleast according to me, for many times I see developers who try to think and build way too many test cases than what is actually needed. Honestly, yours truly too has been guilty of so called BDUF for Unit Test.

Uncle Bob, in formulating the 3 basic rules, emphasis that one needn’t write a unit test no more than when it fails or fails to compile, after which the developer proceeds to write the minimum code required to pass the test case. This short cycles would help the developer adopt the TDD approach in a better way.

Thanks Uncle Bob.

Mock RestClient.ExecuteAsync

RestSharp is not a stranger to developers, so is the usage. What this posts aims to explore is how to do Unit Test a method, involving a RestClient.ExecuteAsync call. To be more precise, the post explores how to mock the RestClient for the method.

Let’s consider the following code, taken from VSPostman, one of the side projects I am working on.

public async Task Get(string url)
{
if (string.IsNullOrWhiteSpace(url) || _parameterDictionary.Values.Contains(null)) throw new ArgumentNullException();

_restClient.BaseUrl = new Uri(url); // _restClient is an instance of RestClient
_restRequest.Method = Method.GET;
var _returnValue = new ResponseObject();
if(_parameterDictionary?.Count==0)
_restRequest.Parameters.AddRange(_parameterDictionary?.Select(x => new Parameter() {Name = x.Key, Value = x.Value }));
var tcs = new TaskCompletionSource();
var watch = Stopwatch.StartNew();
_restClient.ExecuteAsync(_restRequest, response =>
{
_returnValue.ContendType = response.ContentType;
_returnValue.ResponseString = response.Content;
_returnValue.StatusCode = response.StatusCode;
_returnValue.StatusDescription = response.StatusDescription;
_returnValue.ResponseTime = watch.Elapsed;
_returnValue.Length = response.ContentLength;
if (response.Headers != null)
_returnValue.Headers = response.Headers.ToDictionary(x => x.Name, y => y.Value as string);

tcs.SetResult(_returnValue);
});
return await tcs.Task;
}

As observed, the code raises a GET Request using the ReshSharp library. If I was using TDD and writing a Unit Test case for this method, how would I approach it ? How would I mock the RestSharp library calls ?

Of course, it is easy to understand that we need to create the mock object of IRestClient. But how do we create a response ? For this we attach a call back.

var expected = "response content";
var expectedBytes = Encoding.UTF8.GetBytes(expected);
Mock restClient = new Mock();
restClient.Setup(x => x.ExecuteAsync(
It.IsAny(),
It.IsAny<Action<IRestResponse, RestRequestAsyncHandle>>()))
.Callback<IRestRequest, Action<IRestResponse, RestRequestAsyncHandle>>((request, callback) =>
{
callback(new RestResponse { Content= expected }, null);
});
// act
var clientService = new ClientServiceUsingRestSharp(restClient.Object);
var actualResponse = await clientService.Get($"http://www.google.com");

That would be all you need. The callback would ensure you get the response from the mock object.

Unit Testing Asynchronous Code

Unit Testing Asynchronous methods in older days were pretty tough, but thanks to the advances made in the Unit Testing Frameworks, this is now all the more easier.

We will begin by writing an async method, which we could use for writing our Unit Tests against.

public async Task ReadFileAsync(string fileName)
{
  using(var file = File.OpenText(fileName))
     return await file.ReadToEndAsync();
}

The advancements in Unit Testing Framework has made it possible for us to write Unit Tests against async methods easier.

private string _ValidPath = "DummyText.txt";
private string _InvalidPath = "";
private FileReaderAsync _fileReaderAsync;

[TestInitialize]
public void Init()
{
  _ValidPath = @"DemoFile/LargeText.txt";
  _InvalidPath = @"DemoFile/DoNotExist.txt";
  _fileReaderAsync = new FileReaderAsync();
}

[TestMethod]
public async Task FileRead_ValidPath_NoErrors()
{
  var data = await _fileReaderAsync.ReadFileAsync(_ValidPath);
  Assert.IsNotNull(data);
  Assert.IsTrue(data.Length > 0);
}

Handling Exceptions

Let’s add one more Test Case, that would ensure a FileNotFoundException is raised when an invalid path is passed to the method.

[TestMethod]
[ExpectedException(typeof(FileNotFoundException))]
public async Task FileRead_InvalidPath_FileNotFoundException()
{
  var data = await _fileReaderAsync.ReadFileAsync(_InvalidPath);
  Assert.IsNotNull(data);
  Assert.IsTrue(data.Length > 0);
}

This method would, as expected, would succeed with ExpectedException specified. So far, so good. That’s because we expected the exception to be raised from ReadFileAsync method and it did raise an exception when an invalid path was passed.

But what if we had an exception raised from the Arrange Part. For example, we had an exception raised by the Constructor of the class. Let us rewrite Constructor of our Class and the Test Method to demonstrate the issue.

Class under Test

public class FileReaderAsync
{
  public FileReaderAsync(bool throwError=false)
  {
    if (throwError)
       throw new InvalidOperationException();
  }
  public async Task ReadFileAsync(string fileName)
  {
    using(var file = File.OpenText(fileName))
      return await file.ReadToEndAsync();
  }}

Test Method

[TestMethod]
[ExpectedException(typeof(FileNotFoundException))]
public async Task  FileRead_ConstructorOperationNotValidAndFileNotFoundException_NotFound()
{
  _fileReaderAsync = new FileReaderAsync(true);
  var data = await _fileReaderAsync.ReadFileAsync(_InvalidPath);
  Assert.IsNotNull(data);
  Assert.IsTrue(data.Length > 0);
}

This Test method would fail as one would expect. The Expected Exception specified is FileNotFoundException, however, the call to the class Constructor would raise an InvalidOperationException.

Handling Exceptions : Better Approach

What we need is mechanism to specify the expected exception for each specified line of code. NUnit was probably the first framework to make that possible, but MS Test was quick to catch up. Let’s write the Test Method with the new approach.

[TestMethod]
public async Task  FileRead_ConstructorOperationNotValidAndFileNotFoundException_Found()
{
  Assert.ThrowsException(()=>_fileReaderAsync  = new FileReaderAsync(true));
  // Reinitialize as constructor has failed
  _fileReaderAsync = new FileReaderAsync();
  await Assert.ThrowsExceptionAsync(async () =>  await _fileReaderAsync.ReadFileAsync(_InvalidPath));
}

This approach would ensure that the Test Method would pass. As you might have already noticed the Assert.ThrowException has a async counterpart, to take care of the async methods.

This approach is far better than the ExpectedException method as we have already seen. Code samples described in this post is available in my Github.

Mutation Testing

Even with TDD, the quality or coverage of the existing Unit Tests are always a concern for the developer. “Have I done enough ?” That’s one question that haunts him throughout.

Mutation Testing is a structural testing method that attempts to add more value to Unit Tests by aiding in identifying the ‘misses’ in Unit Tests. The central idea behind is to write Mutant of existing code, each of the mutant varying each other by a single change.

For example, Let’s think about the following code.

int TestMethod(int CurrentValue)
{
if(CurrentValue<=3)
return 100;
else
return 200;
}

One of the mutants of the method would be replacing the decision parameter “”.  The Mutant would look like following.

int TestMethod(int CurrentValue)
{
if(CurrentValue>3)
return 100;
else
return 200;
}

Idea behind the Mutation Testing Approach is to ensure that the Testing Data set is near complete and adequate to identify all possibles that the code could have.  The focus would then shift to create Test Cases that would kill as much as mutants as possible.

The mutants can be broadly categorized under 3 categories.
  • Decision Mutations  – Conditions are changed for checking design errors, typically relational, logical and arithmetic operators.
  • Value Mutations  – Typically focused on changing the values (especially constants) to detect the impact/error
  • Statement Mutations – Statements are removed/duplicated.
Thinking about it, Mutation Testing looks a must for any developer if one was turn blind on the extensive resources (mutants) that could be generated. However, from the perspective of a .Net Developer (especially if you are using Visual Studio 2017), there is definite lack of tools and related documentation. Some of the tools that are available for .Net developers are
While Visual Mutator looks the most user friendly, it doesn’t seem to support Visual Studio 2017. Ninja Turtles, on other hand, suffers from lack of documentation and guidance for usage.