Testing of related classes
Video

In real projects, no matter how small they are, we aren't dealing with separate classes, but with chains and bunches of related classes. The depth of such a system might be five/seven/ten+ levels. Testing of such a case may seem a very hard task. And solving of this problem often puts the first sticks of dinamite under the testing code...

Trying to provide work of the testing class, non-experienced developers are tend to write their own fake implementations of classes, on which the tested code depends. Or, which is much worse, they do not fake anything and throw the whole chain of related classes into a test, 'as is', making the testing method/class monstrous and unviable.

The both ways lead to nowhere, the first one because of increasing of fragile of the testing code, the second one - by the reason of complexity of writing and supporting of tests, which is increasing by exponent with growing of number of nesting layers.

By the way, it is necessary to say that the first approach has a right to existing and if we lived in 80ths, it would have become a 'silver bullet'. But nowadays, in the epoch of lazy developers... everyting is already made for us)

Let's look at a simple case with two classes.

We have an interface

public interface IFileReader
{
   string Read( string filePath);
}

A class that implements the interface

public class FileReader
{
   public string Read(string filePath)
   {
      return File.ReadAllText(filePath);
   }
}

And a class, which is dependent on FileReader

public class StringDecorator
{
   private readonly IFileReader _reader;
   public StringDecorator(IFileReader reader)
   {
      _reader = reader;
   }
   
   public string Get(string filePath)
   {
      var text = _reader.Read(filePath);
      return text.ToUpper();
   }
}

Our purpose is to test the class StringDecorator. And the library Moq (from 'Gentleman's set of an autotester') comes to the rescue here. In the easiest case (just to start our aquaintance from somewhere ) we can say that it's mission is creating of fake implementations of interfaces. To not to say to many worthless words, let's look at the example.

[TestFixture]
public class StringDecoratorTests
{
   private <MockIFileReader> _reader;
   private StringDecorator _decorator;
   
   [SetUp]
   public void SetUp()
   {
      _reader =  new Mock<IFileReader>(MockBehavior.Strict);
      _decorator =  new StringDecorator(_reader.Object);
   }
   
   [Theory, AutoData]
   public void Get_Success(string filePath)
   {
      var text = "aaa";
      var expected = "AAA";
      _reader.Setup(e => e.Read(filePath)).Returns(text);
      //
      var result = _decorator.Get(filePath);
      //
      Assert.True(result == expected);
   }
}

*The example uses nUnit, rewriting it for xUnit is valueless, I believe, because removing of [TestFixture] and changing the SetUp method for the constructor would allow to use the code with xUnit.

A talk about SetUp methods is going to be later, at this point it's enough to say that the method executes before each launch of a test in the class StringDecoratorTests.

What's happening inside have to be clear: using the Moq library, we create a fake implementation of IFileReader and use it while creating of an instance of the StringDecorator, which is under testing. Take into account using of MockBehavior.Strict, use this approach everywhere, always - with such a setting we order to system to throw exception every time when the testing code tries to call non-set method of a faked interface. We are going to talk about that while discussing of the Read() method. Variables text and expected don't require any comments - we already told about such things in previous articles.

The whole magic is in the

_reader.Setup(e => e.Read(filePath)).Returns(text);

Here we are setting behaviour of our fake FileReader, ordering that the method Read would had to return the variable 'text', if got the 'filePath' as the input parameter.

From that moment while calling _reader.Read() our _decorator calls our fake and receive exactly the string that we set previously.

The important moment: that is why it is necessary to use the MockBehavior.Strict parameter. If, for any reason, we do not set behaviour of the Read-method, the system would immediately throw an exception at the moment of calling of non-set method and ask for appropriate settings.

If you remove the Strict-parameter, the Read-method would always return null and no exceptions would be thrown... But a developer would curse everything, trying to find out the reason of the broken test)

The next steps are evident, I believe - we execute the tested method and compare the result with the expected string. The code looks pury and there is no need in creating own fakes. Wherein that approach works regardless of the nesting depth of classes. Even including a chain of hundred dependent classes into the FileReader would not influence to our test - we fake the behaviour, described in the interface and don't care about dependences of a concrete class and details of it's realization.