All you need to know about Unit Testing | Minimal Post

Unit/Integration test­ing is some­thing most devel­op­ers do on a dai­ly basis. I had always want­ed to write a blog on this sub­ject — some­thing min­i­mal but that cov­ers the essen­tials and gives one the nec­es­sary ter­mi­nol­o­gy and tools to get going in the begin­ning. This post is tar­get­ed towards junior devel­op­ers. So, here we go -

Rec­om­mend­ed Book — The Art of Unit Test­ing, 2nd Edi­tion

What is Inte­gra­tion Test­ing and what is Unit Test­ing?

Well, soft­ware test­ing comes in two fla­vors, if you will — Unit test­ing and Inte­gra­tion test­ing. What’s the basic dif­fer­ence, you ask? Let’s say you want to test some com­po­nent X of the soft­ware. Most­ly, this X is some method/function. X might depend on some db calls, file sys­tem, etc. If you use real exter­nal resources like a real db call, etc. to test X then you are doing Inte­gra­tion test­ing but instead, if you are fak­ing or stub­bing these exter­nal resources then you are doing Unit test­ing — basi­cal­ly test­ing just that unit X and fak­ing most things it depends on. And it makes sense, you want to test just one sin­gle func­tion­al­i­ty and not depend on any­thing for that.

Using stubs to break depen­den­cies

Your code X ———> depends on some exter­nal depen­den­cy (any object like file sys­tem, threads, db calls, etc.)

But when you want to Unit test X, it would be some­thing like this -

Your code X ———> stub in place of exter­nal depen­den­cy

You might have heard about Mocks, stubs, etc. Don’t get bogged down by ter­mi­nol­o­gy — stubs and mocks are both fakes. And, from The Art of Unit Test­ing — Mocks are like stubs but you assert against a mock but you don’t assert against a stub. We’ll see all this soon.

exam­ple,


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

What­ev­er code is there in place of com­ments above would qual­i­fy as exter­nal depen­den­cy. So, how do we test the above?
One way would be to actu­al­ly read a conf file and use it for test­ing and then destroy it. Of course, this would be time con­sum­ing and would be an inte­gra­tion test and not a Unit test.

So, how to test with­out resort­ing to inte­gra­tion test — The code at hand is direct­ly tied to exter­nal depen­den­cy. It is call­ing the fs direct­ly, we need to first decou­ple this. Some­thing like this,

 

If we do this, our code would not direct­ly depend on fs but on some­thing else that is direct­ly depen­dent on fs. This some­thing else, the fileSys­tem­Man­ag­er in our code is some­thing we that can use for a real fs in case of actu­al code and a fake fs in case of test­ing.

Anoth­er ter­mi­nol­o­gy — Seams

Oppor­tu­ni­ties in code where a dif­fer­ent func­tion­al­i­ty can be plugged in. This can be done through inter­faces (fileSys­tem­Man­ag­er above) so that it can be over­rid­den. Seams are imple­ment­ed through what’s called an Open-Closed prin­ci­ple — class should be open for exten­sion but closed for mod­i­fi­ca­tion.

So, for exam­ple, 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 cre­at­ed a way to bifur­cate between actu­al pro­duc­tion code and tests code. But wait! Remem­ber that we need­ed to test IsValid­File­Name method. The issue is the fol­low­ing line of code -

IFileSys­tem­Man­ag­er mgr = new FileSys­tem­Man­ag­er();
We need to remove this instan­ti­at­ing of a con­crete class that’s using ‘new’ right now. Because if we don’t, no mat­ter where we call IsValid­File­Name from(tests or actu­al code), it will always try to new up FileSys­tem­Man­ag­er. This is where DI con­tain­ers come in but we won’t be going into details of DI here, so let’s take a look at some­thing called ‘con­struc­tor lev­el injec­tion’.


    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 actu­al test now would look some­thing 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 under­stood the above basic con­cept, oth­er stuff will come eas­i­ly. So for instance, here’s one way to make your fakes return an excep­tion -


    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 hand­writ­ten fakes.

But before that, I would like to broad­ly say that test­ing can be of three types (from Art of Unit Test­ing) -
1. Val­ue based — When you want to check whether the val­ue returned is good or not
2. State based — When you want to check whether the code being test­ed has expect­ed state or not (cer­tain vari­ables have been set cor­rect­ly or not)
3. Inter­ac­tion based — When you need to know if code in one object called anoth­er objec­t’s method cor­rect­ly or not. You use it when call­ing anoth­er object is the end result of your unit being test­ed. This is some­thing that you will encounter a lot in real life cod­ing.

Let’s make for­mal dis­tinc­tion between mocks, fakes and stubs now. I will be quot­ing from The Art of Unit Test­ing.
“mock object is a fake object in the sys­tem that decides whether the unit test has passed or failed. It does so by ver­i­fy­ing whether the object under test called the fake object as expect­ed”

A fake is a gener­ic term that can be used to describe either a stub or a mock object (hand­writ­ten or oth­er­wise), 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 cur­rent test. If it’s used to check an inter­ac­tion (assert­ed against), it’s a mock object. Oth­er­wise, it’s a stub.”

Let’s con­sid­er an exam­ple,


//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 mes­sage to our web ser­vice, the mes­sage should be saved as ‘LastEr­ror’. This means that we want to test whether our fake ser­vices’ LogEr­ror method appro­pri­ate­ly updates the ‘LastEr­ror’ 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 anoth­er exam­ple, a bit more involved.

Say your code logs error by call­ing service.LogError, as above. But now, let’s add some more com­plex­i­ty — if service.LogError throws some kind of excep­tion you want to catch it and send an email to some­one to alert that some­thing is wrong with the ser­vice.

Some­thing 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 ques­tions 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 ser­vice should be called
1.2 Mock ser­vice throws an error when we tell it to
1.3 When error is thrown, an email is sent

See if the fol­low­ing will work -
Your test code calls your Log­An­a­lyz­er code inject­ing it a fake web ser­vice that throws an excep­tion when you tell it to. Your log­An­a­lyz­er will also need an email ser­vice that should be called when the excep­tion is thrown.

It should work some­thing like this -

But how will we assert that the email ser­vice was called cor­rect­ly? — Set some­thing in the mock email that we can assert lat­er!


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. Oth­er­wise it usu­al­ly implies that you are test­ing 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 dur­ing test­ing, you could set up a chain of stubs return­ing your val­ue — but that would not be so main­tain­able, would it!
On the oth­er hand, you could also make your code more testable by refac­tor­ing it to be some­thing like -


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

Now, instead of hav­ing to fake a chain of meth­ods, you would only need to do that for Get­Con­nec­tion­String() method.

Prob­lem with hand­writ­ten mocks and stubs

1. Takes time to write
2. Dif­fi­cult to write for big or com­pli­cat­ed inter­faces and class­es
3. Main­te­nance of hand­writ­ten mocks and stubs as code changes
4. Hard to reuse in a lot of cas­es

Enter Iso­la­tion or Mock­ing frame­works

Def­i­n­i­tion — Frame­works to cre­ate and con­fig­ure fake objects at run­time (dynam­ic stubs and mocks)

So, let’s use one! We will be using NSub­sti­tute on Visu­al­Stu­dio for Mac Pre­view edi­tion. This is part of VS IDE devel­oped for .Net Core com­mu­ni­ty.

The code exam­ples men­tioned below are self suf­fi­cient and easy to read. Please go through them in the fol­low­ing order-

1. LogAnalyzerTests.cs — Shows com­par­i­son between writ­ing a hand­writ­ten fake and one using NSub­sti­tute. Also shows how to ver­i­fy the call made to a mock.
2. SimulateFakeReturns.cs — Shows how to make a stub return some­thing we want when­ev­er there is some par­tic­u­lar or gener­ic input.
3. LogAnalyzer2Example.cs — Again shows com­par­i­son between code writ­ten with and with­out a mock­ing frame­work. Shows how to throw excep­tion using NSub­sti­tute.

The above are sim­ple exam­ples show­cas­ing API capa­bil­i­ties of a mock­ing frame­work — things we’ve been doing by hand­writ­ten code till now.

Gen­er­al state­ment on how mock­ing frame­works work — Much the same way you write hand­writ­ten fakes with the excep­tion that these frame­works write code dur­ing run time. At run time, they give us the oppor­tu­ni­ty to over­ride inter­face meth­ods or vir­tu­al meth­ods much the same way we’ve seen till now. Gen­er­at­ing code at run time is not some­thing new to the pro­gram­ming world. How­ev­er, some frame­works allow much more than this — they even allow you to fake con­crete class­es and over­ride their meth­ods. How? In these cas­es the mock­ing frame­works inject code that you want in the .class or .dll and use some­thing IF DEF kind of con­di­tion­al run­ning of code. Of course, this capa­bil­i­ty requires under­stand­ing how to inject or weave code at run time which fur­ther requires under­stand­ing of inter­me­di­ate code tar­get­ing the run­time or VM.

Leave a Reply

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