Click here to Skip to main content
15,885,792 members
Articles / Programming Languages / C#
Article

Example Code And Quick Refresher on Unit Tests with Stubs and Mocks

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
29 Oct 2006CPOL3 min read 22.4K   12  
Example Code And Quick Refresher on Unit Tests with Stubs and Mocks

Introduction

The main purpose of this article is to display the code that is used as reference in the other article. It is also a quick refresher of what I'm looking for in Unit tests that justify the new pattern. So I have written a small class and a test around it that uses stubs and mocks.

So for our example codebase, we have:

  1. A class that is some sort of configuration service which can be called from various places in the code to look up specific entries or settings. The implementation chosen is a singleton.
  2. Some sort of Database Service. Note that while it's not a singleton, we have a factory that can generate instances.
  3. A core class which seems to be doing something important. The StuffDoer.
  4. And lastly our class which we are going to write tests for. The method we're going to test is OurAwesomeMethod().
C#
  public interface IConfigurationService
    {
        string getSetting(string id1, string id2);
    }
    public class ConfigurationService : IConfigurationService
    {
        private ConfigurationService()
        {
        }

        private static IConfigurationService _instance;

        public static IConfigurationService instance
        {
            get
            {
                if (_instance == null)
                    _instance = new ConfigurationService();
                return _instance;
            }
            set { _instance = value; }
        }
        public string getSetting(string id1, string id2)
        {
            string blah = null;
            // complex code here;
            return blah;
        }
    }
  
public interface IDataService
    {
        bool IsUserValid(string name, string password);
    }

    public class DataService : IDataService
    {
        string _connectionString;
        public bool IsUserValid(string name, string password)
        {
            bool value = false;
            string.Concat(_connectionString, name);
            //complex checks here;
            return value;
        }

        public DataService(string connectionString)
        {
            _connectionString = connectionString;
        }
    }

    public static class ServiceFactory
    {
        private static IDataService _instance = null;

        public static IDataService GetGetDataService(string connectionString)
        {
            if (_instance == null)
                return new DataService(connectionString);
            else return _instance;
        }

        public static void InjectForTest(IDataService testDataService)
        {
            _instance = testDataService;
        }
    }
 
   public interface IstuffDoer
    {
        string DoStuff(string input);
    }
   public class StuffDoer : IstuffDoer
    {
        private string _connectionString;


        public StuffDoer(string connectionString)
        {
            _connectionString = connectionString;
        }

        public string DoStuff(string input)
        {
            string output = "1" + input + "0";
            //complex code here
            IDataService dataService = 
                ServiceFactory.GetGetDataService(_connectionString);
            // calls to dataservice here
            return output;
        }
    }

        public class OurClass
    {
        private string _connectionStringSettingName;
        private int _minUsers;

        public OurClass(string connectionStringSettingName, int minUsers)
        {
            _connectionStringSettingName = connectionStringSettingName;
            _minUsers = minUsers;
        }

        public string OurAwesomeMethod(Dictionary<STRING, string> users)
        {
            string connectionString = ConfigurationService.instance.getSetting
                    ("db", _connectionStringSettingName);
            List<STRING> validUsers = getValidUsers(connectionString, users);
            if (validUsers.Count < _minUsers)
                throw new ApplicationException("not enough users");
            IstuffDoer stuffDoer = new StuffDoer(connectionString);
            string output = string.Empty;
            foreach (string user in validUsers)
            {
                 output = output + stuffDoer.DoStuff(user);
            }
            return output;
        }

        private static List<STRING> getValidUsers(string connectionString,
                                                  Dictionary<STRING, string> users)
        {
            IDataService dataService = 
                ServiceFactory.GetGetDataService(connectionString);
            List<STRING> validUsers = new List<STRING>();
            foreach (KeyValuePair<STRING, string> user in users)
            {
                string userName = user.Key;
                string password = user.Value;
                if (dataService.IsUserValid(userName, password))
                {
                    validUsers.Add(userName);
                }
            }
            return validUsers;
        }
    }

Our purpose here is to test OurClass.OurAwesomeMethod(). Generally, it is accepted that tests should be small and test very specific behaviours/execution branches of a method. The complete set of tests around a method are used to validate the behavior of this method. So specifically unit tests have two purposes:

  • Small discrete tests checking specific behaviors of a method
  • Small discrete tests documenting specific behaviors of a method

So as a result, normally tests must be:

  • Correct - when the method doesn't do what is intended, the test must break.
  • Robust - when the method continues to behave right despite changes in implementation, the test should not break as far as possible.
  • Clear - When someone reads a test, it should be possible to ascertain one additional thing that the method that is being tested does.

Ok, given all of this, the next step is to write a test. The assumption being made here is that we want to separate all the decoupled logic and test the behavior we have. There are three calls being made outside our own class. The dataservice and the configurationservice can easily be dealt with by sending in mocks. However, as we can see, the call to StuffDoer() is a little harder. This calls for some minor refactoring of our method and a stub. The refactoring is easily done. So our new and improved OurAwesomeMethod() is as follows:

C#
public string OurAwesomeMethod(Dictionary<STRING, string> users)
{
    string connectionString = ConfigurationService.instance
        .getSetting("db", _connectionStringSettingName);
    List<STRING> validUsers = getValidUsers(connectionString, users);
    if (validUsers.Count < _minUsers)
        throw new ApplicationException("not enough users");
    string output = string.Empty;
    IstuffDoer stuffDoer = getStuffDoer(connectionString);
    foreach (string user in validUsers)
    {
        output = output + stuffDoer.DoStuff(user);
    }
    return output;
}

protected virtual IstuffDoer getStuffDoer(string connectionString)
{
    return new StuffDoer(connectionString);
}

Now a stub can easily prevent a real call from going through. So we can build such a stub inside our testclass:

C#
private class OurClassStub : OurClassRefactored
{
    private IstuffDoer _stuffDoer;

    public OurClassStub(string connectionStringSettingName, 
    int minUsers, IstuffDoer stuffDoer)
    : base(connectionStringSettingName, minUsers)
    {
        _stuffDoer = stuffDoer;
    }

    protected override IstuffDoer getStuffDoer(string connectionString)
    {
        return _stuffDoer;
    }
}

Now when we call OurAwesomeMethod(), we can make sure that all external calls are sent to mock objects. For the purpose of this document, I've decided to use RhinoMocks since they are pretty cool. They have good support for refactoring which is definitely something I'm interested in. So let's see what we can say about OurAwesomeMethod().

  1. We know that everytime we call it, it really needs the connectionString. So it's going to ask the Configuration Service for it. We don't care how many times it asks for it or when. All we care is that if it asks for a connectionString we give it one. We're gonna pass to OurAwesomeMethod() a dictionary with username/passwords and we need to tell our dataservice to return information about validity of each such combination. And now if we create OurClass() with a lower tolerance for valid users than there are, it must throw an exception.
  2. We also know that when we do the same thing as before but the number of users who it checks the validity of is greater than or equal to its tolerance, it must call StuffDoer to do stuff on each of these and then return a concatenated list of all these users.

So let's write up the test class to test both these scenarios:

C#
[TestFixture]
public class OurClassTest
{
    private MockRepository _mocks;
    private IConfigurationService _configurationService;
    private IDataService _dataService;
    private IstuffDoer _stuffDoer;
    private OurClassStub _ourClassStub;
    private const string _connectionStringSettingName = "settingName";
    private const int _tolerance = 3;
    private const string _connectionString = "connectionString";
    private Dictionary<string, string> _users;

    [SetUp]
    public void SetUp()
    {
        _mocks = new MockRepository();
        _configurationService = (IConfigurationService)
                                    _mocks.CreateMock(typeof (IConfigurationService));
        ConfigurationService.instance = _configurationService;
        _dataService = (IDataService)
        _mocks.CreateMock(typeof (IDataService));
        _stuffDoer = (IstuffDoer) _mocks.CreateMock(typeof (IstuffDoer));
        ServiceFactory.InjectForTest(_dataService);
        _users = getUserList();
        _ourClassStub = new OurClassStub(_connectionStringSettingName,
        _tolerance, _stuffDoer);
        SetupResult.On(_configurationService).Call(_configurationService.
        getSetting("db", _connectionStringSettingName)).Return(
        _connectionString);
    }

    private Dictionary<string, string> getUserList()
    {
        Dictionary<string, string> users = new Dictionary<string, string>();
        users.Add("user1", "pass");
        users.Add("user2", "pass");
        users.Add("user3", "pass");
        users.Add("user4", "pass");
        return users;
    }

    private void setupDataServiceResult(string name, string pass, bool result)
    {
        SetupResult.On(_dataService).Call(_dataService.IsUserValid(name, pass))
        .Return(result);
    }

    [Test, ExpectedException(typeof(ApplicationException),"not enough users")]
    public void OurAwesomeMethodThrowsExceptionIfMinimumValidUsersDontExist()
    {
        setupDataServiceResult("user1", "pass", true);
        setupDataServiceResult("user2", "pass", false);
        setupDataServiceResult("user3", "pass", false);
        setupDataServiceResult("user4", "pass", true);
        _mocks.ReplayAll();
        _ourClassStub.OurAwesomeMethod(_users);
    }

    [TearDown]
    public void TearDown()
    {
        _mocks.VerifyAll();
        ConfigurationService.instance = null;
        ServiceFactory.InjectForTest(null);
    }

    private void setupStuffDoerResult(string name)
    {
        SetupResult.On(_stuffDoer).Call(_stuffDoer.DoStuff(name))
        .Return(name + " ");
    }

    [Test]
    public void OurAwesomeMethodReturnsConcatenatedUsersListWhenEnoughUsersAreValid()
    {
        setupDataServiceResult("user1", "pass", true);
        setupDataServiceResult("user2", "pass", false);
        setupDataServiceResult("user3", "pass", true);
        setupDataServiceResult("user4", "pass", true);
        setupStuffDoerResult("user1");
        setupStuffDoerResult("user3");
        setupStuffDoerResult("user4");
        _mocks.ReplayAll();
        string output = _ourClassStub.OurAwesomeMethod(_users);
        Assert.AreEqual("user1 user3 user4 ",output);
    }
}

Well, that's all the ground I want to set for unit testing, stubs and mocks. And now on to the special cases.

History

  • 30th October, 2006: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --