Writing Unit Test Using XUnit And Mocking Frameworks (NSubstitue/FakeItEasy)

Introduction

 
In this article, I will be explaining why unit testing is important and the challenges and problems that are faced during writing unit tests and how to use mocking frameworks like NSubstitue and FakeItEasy to Mock objects.
 

Why Unit Test?

 
In the programming world, unit testing is a software testing method by which individual units of source code are tested to determine whether they are suitable for use. A unit is the smallest testable software component. Usually, it performs a single organized function. A unit is small, so it is easier to design, execute, record, and analyze test results than for larger chunks of code. Defects revealed by a unit test are easy to find and relatively easy to fix. Unit testing is testing that is designed to verify individual modules (functions, classes, etc.) at a low level, to verify that module is behaving as specified.
 

Why is unit testing challenging?

  • Complex code
  • Code that is Coupled with Other classes
  • Code that accesses to External resource i.e. web service, data base object etc.

Good unit test characteristics

  • Atomic – Unit test must have small functionality to test.
  • Deterministic - Test should pass or fail not be inconclusive.
  • Repeatable – Test should pass consistently, if test fails without changing code then it is not repeatable.
  • Order Independent - Test should not run in any order to pass.
  • Fast - Unit test should be fast, if any test take ~1sec to run then it considers as too slow.
  • Easy to Set up. 

Most common problem with UT

 
The most common problem of unit tests is dependency of objects to each other, method to test have dependency to first class and then first class has dependency to the other two classes as shown below, this is most common problem where you will end up creating mock classes for each dependent class. That is called hand rolled mocks.
 
 
Solution of this is to mock first and second class objects' return values.
 

Problems with Hand Rolled Mocking

 
You can still write unit tests without the need of any mocking framework; i.e. calling hand rolled mocking where you create mock classes of each dependent class to mock dependency, but it has its own disadvantages as mentioned below:
  • Fake Implementation of abstraction.
  • Create classes by hand.
  • Each Mock object adds more code and complexity.
  • Conditional check on Mock classes.
  • Interface changes.
  • Time/Maintenance of code. 

What is FakeItEasy/NSubstitute?

 
A .NET dynamic framework for creating all types of fake objects which is designed for ease of use and compatibility with both C# and VB.NET.
 

Difference in Syntax of these two frameworks

 
 

What can be faked

  • Interfaces
  • Classes that
    • are not sealed,
    • are not static, and
    • have at least one public or protected constructor whose arguments FakeItEasy can construct or obtain
  • Delegates 
Let's understand the syntax of both the framework and how to use it. I will be explaining one by one in detail. These are topics which are most commonly used during writing any unit test.
  • AAA Syntax
  • Verification/Asserts
  • Return Values
  • Parameters
  • Exceptions
  • Properties
To start with Unit test, I have created a small application for Addition and a max of two values which take two inputs and return the result, but the trick is this method also has some web service to post the data. Here, I will explain how to mock that web service and the result of it. Here is the sample code. 
  1. public interface IMathService  
  2.  {  
  3.    int Add(int a, int b);  
  4.    void Add(int a, int b, out int c);
  5.    Task<int> Max(int a, int b);  
  6.  }  
  7.   
  8.  public class MathInstance : IMathService  
  9.  {  
  10.    private readonly IWebService _webService;  
  11.   
  12.    public MathInstance() : this(null)  
  13.    {  
  14.    }  
  15.   
  16.    public MathInstance(IWebService webService)  
  17.    {  
  18.      _webService = webService;  
  19.    }  
  20.   
  21.    public int Add(int a, int b)  
  22.    {  
  23.      return a + b;  
  24.    }
  25.    
  26.    public void Add(int a, int b, out int c) => c = a + b;  
  27.   
  28.    public async Task<int> Max(int a, int b)  
  29.    {  
  30.      var maxvalue = Math.Max(a, b);  
  31.      await _webService.Post("https://unknowncaller.com", maxvalue, new CancellationToken());  
  32.      return maxvalue;  
  33.    }  
  34.  }   
  1. public class WebService : IWebService  
  2.  {  
  3.    public async Task Post<T>(string uriString, T c, CancellationToken ct)  
  4.    {  
  5.      var client1 = new HttpClient();  
  6.   
  7.      var uri = new Uri(uriString);  
  8.      client1.DefaultRequestHeaders.Accept.Clear();  
  9.      var json = JsonConvert.SerializeObject(c);  
  10.      var data = new StringContent(json, Encoding.UTF8, "application/json");  
  11.   
  12.      var response = await client1.PostAsync(uri, data, ct);  
  13.      ct.ThrowIfCancellationRequested();  
  14.   
  15.      response.EnsureSuccessStatusCode();  
  16.   
  17.   
  18.      await response.Content.ReadAsStringAsync();  
  19.    }  
  20.  }  
Lets start writing Unit tests of the above program.
 

Unit Tests Implementation using Xunit and Mocking Frameworks (FakeItEasy & NSubstitute)

 
I will be using xunit test framework to write unit tests, it uses Fact for a single test and Theory with (InlineData) to test multiple conditions in a single test.
 
AAA Syntax
 
A basic test of to demonstrate AAA Syntax. 
  1.     [Fact]  
  2.     public void AddTest()  
  3.     {  
  4.       // Arrange  
  5.       var obj = new MathInstance();  
  6.   
  7.       // Act  
  8.       var actualResult = obj.Add(3, 2);  
  9.   
  10.       // Assert  
  11.       Assert.Equal(5, actualResult);  
  12.     }  
  1.     [Theory]  
  2.     [InlineData(1, 2, 3)]  
  3.     [InlineData(1, 13, 14)]  
  4.     public void MultipleAddTest(int x, int y, int result)  
  5.     {  
  6.     }  
Creation of Mock Object, passing parameters and faking return values.
 
Let's write a test for Max method where it uses web service to post the call of max value. We need mock web service here to post the fake value so that our test of max is executed without any interruption.
 
using FakeItEasy
  1.     [Fact]  
  2.     public async void MaxTest()  
  3.     {  
  4.       // Arrange  
  5.       var webservice = A.Fake<IWebService>();  // fake web service by using A.Fake<T>
  6.       var obj = new MathInstance(webservice);  
  7.       A.CallTo(() => webservice.Post(A<string>._, A<int>._, A<CancellationToken>._)).Returns(Task.Delay(1));  // Params are passed A<T>._ or A<T>.Ignored
  8.   
  9.       // Act  
  10.       var actualResult = await obj.Max(2, 5);  
  11.   
  12.       // Assert  
  13.       Assert.Equal(5, actualResult);  
  14.       A.CallTo(() => webservice.Post(A<string>._, A<int>._, A<CancellationToken>._)).MustHaveHappened();  // Assert/verification of Mock object call
  15.     }  
using NSubstitue
  1.     [Fact]  
  2.     public async void MaxTest()  
  3.     {  
  4.       // Arrange  
  5.       var webservice = Substitute.For<IWebService>();  // fake web service by using Substitute.For<T>()
  6.       var obj = new MathInstance(webservice);  
  7.       webservice.Post(Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>()).Returns(Task.Delay(1));  // Params are passed Arg.Any<T>() or Arg.Is<T>()
  8.   
  9.       // Act  
  10.       var actualResult = await obj.Max(2, 5);  
  11.   
  12.       // Assert  
  13.       Assert.Equal(5, actualResult);  
  14.       await webservice.Received().Post(Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>());  // Assert/verification of Mock object call
  15.     }  

Out Parameters

 
Using FakeItEasy 
  1.    [Fact]  
  2.    public void FakeAdd_OutPrams_Test()  
  3.    {  
  4.      // Arrange  
  5.      int c = 0;  
  6.      var obj = A.Fake<IMathService>();  
  7.      A.CallTo(() => obj.Add(A<int>._, A<int>._, out c)).Invokes(() => c = 7);  // faking Void method call and assign out params
  8.   
  9.      // Act  
  10.      obj.Add(3, 2, out _);  
  11.   
  12.      // Assert  
  13.      Assert.Equal(7, c);  
  14.      A.CallTo(() => obj.Add(A<int>._, A<int>._, out c)).MustHaveHappened();  
  15.      A.CallTo(() => obj.Add(A<int>._, A<int>._)).MustNotHaveHappened();  
  16.    }  
Using NSubstitue
  1.     [Fact]  
  2.     public void FakeAdd_OutPrams_Test()  
  3.     {  
  4.       // Arrange  
  5.       var obj = Substitute.For<IMathService>();  
  6.       obj.When(s => s.Add(Arg.Any<int>(), Arg.Any<int>(), out _)).Do(s => { s[2] = 7; });  // faking Void method call and assign out params
  7.   
  8.       // Act  
  9.       obj.Add(3, 2, out int result);  
  10.   
  11.       // Assert  
  12.       Assert.Equal(7, result);  
  13.       obj.ReceivedWithAnyArgs().Add(0, 0, out _);  
  14.       obj.DidNotReceive().Add(Arg.Any<int>(), Arg.Any<int>());  
  15.     }  

Exception And Events

 
Let's add a new event in the IMathService interface  to test this functionality.
 
event EventHandler<EventArgs> AddDone;
 
Using FakeItEasy  
  1.     [Fact]  
  2.     public void FakeAddTest_Exception()  
  3.     {  
  4.       // Arrange  
  5.       var obj = A.Fake<IMathService>();  
  6.       A.CallTo(() => obj.Add(A<int>._, A<int>._)).Throws<InvalidOperationException>();  
  7.   
  8.       // Act  
  9.       Action actualResult = () => obj.Add(3, 2);  
  10.   
  11.       // Assert  
  12.       Assert.Throws<InvalidOperationException>(actualResult);  
  13.     }  
  14.   
  15.     [Fact]  
  16.     public void FakeAddTest_Event()  
  17.     {  
  18.       // Arrange  
  19.       var obj = A.Fake<IMathService>();  
  20.       obj.AddDone += Raise.With(thisnew EventArgs());  
  21.   
  22.       // Act  
  23.       var actualResult = obj.Add(3, 2);  
  24.   
  25.       // Assert  
  26.       Assert.Equal(5, actualResult);  
  27.     }  
using NSubstitute
  1.    [Fact]  
  2.    public void FakeAddTest_Exception()  
  3.    {  
  4.      // Arrange  
  5.      var obj = Substitute.For<IMathService>();  
  6.      obj.Add(Arg.Any<int>(), Arg.Any<int>()).Throws<InvalidOperationException>();  
  7.   
  8.      // Act  
  9.      Action actualResult = () => obj.Add(3, 2);  
  10.   
  11.      // Assert  
  12.      Assert.Throws<InvalidOperationException>(actualResult);  
  13.    }  
  14.   
  15.    [Fact]  
  16.    public void FakeAddTest_Event()  
  17.    {  
  18.      // Arrange  
  19.      var obj = Substitute.For<IMathService>();  
  20.      obj.AddDone += Raise.EventWith(new EventArgs());  
  21.   
  22.      // Act  
  23.      var actualResult = obj.Add(3, 2);  
  24.   
  25.      // Assert  
  26.      Assert.Equal(5, actualResult);  
  27.    }  

Conclusion 

 
Here, we learned the importance of Unit test and the  challenges that are  faced during UT and the disadvantage of the hand rolled model, we also learned how to mock objects using FakeItEasy and NSubstitue framework and mock return values, event and exceptions. Both frameworks are good in mocking generation with different syntax and work in a similar fashion. NSubstitute looks much cleaner in terms of lines of code. I hope you liked the article and learned something today. Happy learning. :)