All you need to know about Unit Testing | Minimal Post

Unit/Integration testing is something most developers do on a daily basis. I had always wanted to write a blog on this subject – something minimal but that covers the essentials and gives one the necessary terminology and tools to get going in the beginning. This post is targeted towards junior developers. So, here we go –

Recommended Book – The Art of Unit Testing, 2nd Edition

What is Integration Testing and what is Unit Testing?

Well, software testing comes in two flavors, if you will – Unit testing and Integration testing. What’s the basic difference, you ask? Let’s say you want to test some component X of the software. Mostly, this X is some method/function. X might depend on some db calls, file system, etc. If you use real external resources like a real db call, etc. to test X then you are doing Integration testing but instead, if you are faking or stubbing these external resources then you are doing Unit testing – basically testing just that unit X and faking most things it depends on. And it makes sense, you want to test just one single functionality and not depend on anything for that.

Using stubs to break dependencies

Your code X ———> depends on some external dependency (any object like file system, threads, db calls, etc.)

But when you want to Unit test X, it would be something like this –

Your code X ———> stub in place of external dependency

You might have heard about Mocks, stubs, etc. Don’t get bogged down by terminology – stubs and mocks are both fakes. And, from The Art of Unit Testing – Mocks are like stubs but you assert against a mock but you don’t assert against a stub. We’ll see all this soon.

example,


public bool IsValidFileName(String fn)
{
    //read some conf file from file system
    //to see if extension is supported by your company or not.
}

Whatever code is there in place of comments above would qualify as external dependency. So, how do we test the above?
One way would be to actually read a conf file and use it for testing and then destroy it. Of course, this would be time consuming and would be an integration test and not a Unit test.

So, how to test without resorting to integration test – The code at hand is directly tied to external dependency. It is calling the fs directly, we need to first decouple this. Something like this,

 

If we do this, our code would not directly depend on fs but on something else that is directly dependent on fs. This something else, the fileSystemManager in our code is something we that can use for a real fs in case of actual code and a fake fs in case of testing.

Another terminology – Seams

Opportunities in code where a different functionality can be plugged in. This can be done through interfaces (fileSystemManager above) so that it can be overridden. Seams are implemented through what’s called an Open-Closed principle – class should be open for extension but closed for modification.

So, for example, from the above we can have –


    public class FileSystemManager: IFileSystemManager
    {
        public bool IsValid(String fileName)
        {
         ..some production code interacting with real fs 
        }
    }

So, now your code looks something like this - 

    public bool IsValidFileName(String fileName)
    {
        IFileSystemManager mgr = new FileSystemManager();
        return mrg.IsValid(fileName);
    }

So, that is your code but what to use in tests? – a stub!


    public class FakeFileSystemManager: IFileSystemManager
    {
        public bool IsValid(String fileName)
        {
            return true; //no need to interact with real fs
        }
    }

So, ok, good, we have created a way to bifurcate between actual production code and tests code. But wait! Remember that we needed to test IsValidFileName method. The issue is the following line of code –

IFileSystemManager mgr = new FileSystemManager();
We need to remove this instantiating of a concrete class that’s using ‘new’ right now. Because if we don’t, no matter where we call IsValidFileName from(tests or actual code), it will always try to new up FileSystemManager. This is where DI containers come in but we won’t be going into details of DI here, so let’s take a look at something called ‘constructor level injection’.


    public classUnderTest
    {
        private IFileSystemManager _mgr;
        public classUnderTest(IFileSystemManager mgr)
        {
            _mgr = mgr 
        }

        public IsValidFileName(String fileName)
        {
            return _mgr.IsValid(fileName); //so just like that we got rid of new! and this
            //code is ready for testing - we will send in fake fileSystemManager in case of tests and
            //'real' fileSystem in case of production code
        }
    }

So, the actual test now would look something like –


    [TestFixture]
    public class ClassUnderTest_Tests
    {

     [Test]
     public void
     IsValidFileName_NameSupportedExtension_ReturnsTrue()
     {
         IFileSystemManager myFakeManager =
                 new FakeFileSystemManager();
         myFakeManager.WillBeValid = true;
         ClassUnderTest obj = new ClassUnderTest (myFakeManager);
         bool result = obj.IsValidFileName("short.ext");
         Assert.True(result);
     }
    }
    internal class FakeFileSystemManager : IFileSystemManager
    {
        public bool WillBeValid = false;
        public bool IsValid(string fileName)
        {
            return WillBeValid;
        } 
     }

Once you’ve understood the above basic concept, other stuff will come easily. So for instance, here’s one way to make your fakes return an exception –


    class FakeFileSystemManager: IFileSystemManager
    {
        public bool WillBeValid = false;
        public Exception WillThrow = null;

        public bool IsValid(String fileName)
        {
            if(WillThrow != null) //where WillThrow can be configured from the calling code.
                throw WillThrow;
            return WillBeValid;
        }
    }

Till now we have seen how to write our own fakes but let’s see how to avoid handwritten fakes.

But before that, I would like to broadly say that testing can be of three types (from Art of Unit Testing) –
1. Value based – When you want to check whether the value returned is good or not
2. State based – When you want to check whether the code being tested has expected state or not (certain variables have been set correctly or not)
3. Interaction based – When you need to know if code in one object called another object’s method correctly or not. You use it when calling another object is the end result of your unit being tested. This is something that you will encounter a lot in real life coding.

Let’s make formal distinction between mocks, fakes and stubs now. I will be quoting from The Art of Unit Testing.
“mock object is a fake object in the system that decides whether the unit test has passed or failed. It does so by verifying whether the object under test called the fake object as expected”

“A fake is a generic term that can be used to describe either a stub or a mock object (handwritten or otherwise), because they both look like the real object. Whether a fake is a stub or a mock depends on how it’s used in the current test. If it’s used to check an interaction (asserted against), it’s a mock object. Otherwise, it’s a stub.”

Let’s consider an example,


//an interface
public interface IWebService
{
    void LogError(string message);
}

and a hand-written fake

//note that this will only be a mock once we use it in some assert in some test
public class FakeWebService:IWebService
{
    public string LastError;
    public void LogError(string message)
    {
        LastError = message;
    }
}

What we want to test – When we encounter an error and post the message to our web service, the message should be saved as ‘LastError’. This means that we want to test whether our fake services’ LogError method appropriately updates the ‘LastError’ or not.


[Test]
 public void Analyze_TooShortFileName_CallsWebService()
  {
     FakeWebService mockService = new FakeWebService();
     LogAnalyzer log = new LogAnalyzer(mockService);
     string tooShortFileName="abc.ext";
     log.Analyze(tooShortFileName);
     StringAssert.Contains("Filename too short:abc.ext",
                        mockService.LastError); // now mockService is a 'mock'
}
 public class LogAnalyzer
 {
    private IWebService service;
 
    public LogAnalyzer(IWebService service)
    {
        this.service = service;
    }
    public void Analyze(string fileName)
    {
        if(fileName.Length<8)
        {
           service.LogError("Filename too short:"
           + fileName);
        }  
    }
}

Let’s take another example, a bit more involved.

Say your code logs error by calling service.LogError, as above. But now, let’s add some more complexity – if service.LogError throws some kind of exception you want to catch it and send an email to someone to alert that something is wrong with the service.

Something like this,

 

if(fileName.Length < 8)
{
    try
    {
        service.LogError("too short " + fileName);
    }
    catch(Exception e)
    {
        email.SendEmail("something wrong with service", e.Message);
    }
}

Now, there are two questions in front of us –
1. What do we want to test?
2. How do we test it?

Let’s answer them –
What do you want to test
1.1 When there is an error like short file name then mock web service should be called
1.2 Mock service throws an error when we tell it to
1.3 When error is thrown, an email is sent

See if the following will work –
Your test code calls your LogAnalyzer code injecting it a fake web service that throws an exception when you tell it to. Your logAnalyzer will also need an email service that should be called when the exception is thrown.

It should work something like this –

But how will we assert that the email service was called correctly? – Set something in the mock email that we can assert later!


class EmailInfo
{
    public string Body;
    public string To;
    public string Subject;
}

[Test]
public void Analyze_WebServiceThrows_SendsEmail()
{
    FakeWebService stubService = new FakeWebService();
    stubService.ToThrow=  new Exception("fake exception");
    FakeEmailService mockEmail = new FakeEmailService();
    LogAnalyzer2 log = new LogAnalyzer2(stubService,mockEmail);
    string tooShortFileName="abc.ext";
    log.Analyze(tooShortFileName);

    EmailInfo expectedEmail = new EmailInfo {
                                       Body = "fake exception",
                                       To = "someone@somewhere.com",
                                       Subject = "can’t log" }

   Assert.AreEqual(expectedEmail, mockEmail.email);
}

public class FakeEmailService:IEmailService
{
    public EmailInfo email = null;
    public void SendEmail(EmailInfo emailInfo)
    {
        email = emailInfo;
    }
}

public interface IEmailService
{
    void SendEmail(string to, string subject, string body);
}

public class LogAnalyzer2
{
    public LogAnalyzer2(IWebService service, IEmailService email)
    {
         Email = email,
         Service = service;
    }
    public IWebService Service
    {
         get ;
         set ; 
    }
    public IEmailService Email
    {
        get ;
        set ; 
    }

    public void Analyze(string fileName)
    {
        if(fileName.Length<8)
        {
            try {
                  Service.LogError("Filename too short:" + fileName);
                }
                catch (Exception e)
                {
                    Email.SendEmail("someone@somewhere.com",
                                    "can’t log",e.Message);
                }            
        }
    }
}

public class FakeWebService:IWebService
{
    public Exception ToThrow;
    public void LogError(string message)
    {
        if(ToThrow!=null)
        {
            throw ToThrow;
        }
    } 
}

Rule of thumb – once mock per test. Otherwise it usually implies that you are testing more than one thing in a Unit test.

Let’s say you have some code like –


String connString = GlobalUtil.Configuration.DBConfiguration.ConnectionString

and you want to replace connString with one of your own during testing, you could set up a chain of stubs returning your value – but that would not be so maintainable, would it!
On the other hand, you could also make your code more testable by refactoring it to be something like –


String connString = GetConnectionString();
..
..
public String GetConnectionString()
{
    return GlobalUtil.Configuration.DbConfiguration.ConnectionString;
}

Now, instead of having to fake a chain of methods, you would only need to do that for GetConnectionString() method.

Problem with handwritten mocks and stubs

1. Takes time to write
2. Difficult to write for big or complicated interfaces and classes
3. Maintenance of handwritten mocks and stubs as code changes
4. Hard to reuse in a lot of cases

Enter Isolation or Mocking frameworks

Definition – Frameworks to create and configure fake objects at runtime (dynamic stubs and mocks)

So, let’s use one! We will be using NSubstitute on VisualStudio for Mac Preview edition. This is part of VS IDE developed for .Net Core community.

The code examples mentioned below are self sufficient and easy to read. Please go through them in the following order-

1. LogAnalyzerTests.cs – Shows comparison between writing a handwritten fake and one using NSubstitute. Also shows how to verify the call made to a mock.
2. SimulateFakeReturns.cs – Shows how to make a stub return something we want whenever there is some particular or generic input.
3. LogAnalyzer2Example.cs – Again shows comparison between code written with and without a mocking framework. Shows how to throw exception using NSubstitute.

The above are simple examples showcasing API capabilities of a mocking framework – things we’ve been doing by handwritten code till now.

General statement on how mocking frameworks work – Much the same way you write handwritten fakes with the exception that these frameworks write code during run time. At run time, they give us the opportunity to override interface methods or virtual methods much the same way we’ve seen till now. Generating code at run time is not something new to the programming world. However, some frameworks allow much more than this – they even allow you to fake concrete classes and override their methods. How? In these cases the mocking frameworks inject code that you want in the .class or .dll and use something IF DEF kind of conditional running of code. Of course, this capability requires understanding how to inject or weave code at run time which further requires understanding of intermediate code targeting the runtime or VM.

Leave a Reply

Your email address will not be published. Required fields are marked *