Mocking Exceptions and Events with JustMock Free Edition

.NET Musings

Wandering thoughts of a developer, architect, speaker, and trainer

NAVIGATION - SEARCH

Mocking Exceptions and Events with JustMock Free Edition

Mocking Exceptions

When writing unit tests, it is important to cover not only the successful execution path (commonly referred to as the “Happy” path), but also test the code that gets executed when things don’t go right (commonly referred to as the “Unhappy” path).  Testing for situations where services or databases are unavailable as well as erroneous user input are important aspects of ensuring quality in software.  Remember, if you don’t test it, your users will!

We modify the LoginUser method to throw a custom exception when there is an exception is thrown from the LoginService.  To test this, we need to be able to throw an exception from our mock, and test for the custom exception in the unit test.

public bool LoginUser(string userName, string password)
{
    try
    {
        UserID = _service.ValidateUser(userName, password);
    }
    catch (Exception ex)
    {
        throw new MyServiceException(ex.Message);
    }
    if (UserID != 0)
    {
        ShoppingCart = _cartRepo.LoadCart(UserID);
        return true;
    }
    return false;
}

This is extremely simple with JustMock and NUnit.  In the following test, we setup an expectation that the code will throw a custom exception (of type MyServiceException) and the message in the exception will be “Unable to contact the Login Service”.

We then arrange the mock of the ILoginService to throw a system exception with the message that we are expecting, including the message for the exception. We can then validate that our line of business code behaves exactly as expected when all does not go as planned.

[Test,
ExpectedException(typeof(MyServiceException), 
    ExpectedMessage = "Unable to contact the Login Service")]
public void Should_Throw_An_Exception_With_Negative_Parameter()
{
    ILoginService service = Mock.Create<ILoginService>();
    Mock.Arrange(() => service.ValidateUser(string.Empty, string.Empty))
        .IgnoreArguments()
        .Throws<System.Exception>("Unable to contact the Login Service");
    SecurityHandler handler = new SecurityHandler(service, null);
    handler.LoginUser("foo", "bar");
}

Mocking Events

Handling events in your system under test can be tricky.  Suppose your service raises an event, and you want to log that event (for example to track when user’s have logged on).  Normally, you would probably log this to an event log or some other tracking mechanism, but for the sake of simplicity, we are just going to use a struct to represent the logging.  The new SecurityHandler and the UserLoginEventLog struct look like this :

public class SecurityHandler
{
    private readonly ILoginService _service;
    private readonly IShoppingCartRepository _cartRepo;
    private Cart _shoppingCart;
    public Cart ShoppingCart
    {
        get
        {
            if (_shoppingCart == null && UserID != 0)
            {
                _shoppingCart = _cartRepo.LoadCart(UserID);
            }
            return _shoppingCart;
        }
        set
        {
            _shoppingCart = value;
        }
    }
    public int UserID { get; internal set; }
    public SecurityHandler(
        ILoginService service, IShoppingCartRepository cartRepo)
    {
        _cartRepo = cartRepo;
        _service = service;
        _service.UserLoggedOnEvent += Service_UserLoggedOnEvent;
    }
    void Service_UserLoggedOnEvent(
        object sender, UserLoggedOnEventArgs eventArgs)
    {
        EventLog = new UserLoginEventLog(
            eventArgs.UserName, eventArgs.TimeLoggedOn, eventArgs.UserID);
    }
    public UserLoginEventLog EventLog { get; set; }
    public bool LoginUser(string userName, string password)
    {
        try
        {
            UserID = _service.ValidateUser(userName, password);
        }
        catch (Exception ex)
        {
            throw new MyServiceException(ex.Message);
        }
        if (UserID != 0)
        {
            ShoppingCart = _cartRepo.LoadCart(UserID);
            return true;
        }
        return false;
    }
    public bool AddProductToCart(int productID)
    {
        if (_cartRepo.InventoryRepository.ValidateInventory(productID))
        {
            ShoppingCart.Items.Add(productID);
            return true;
        }
        return false;
    }
}
public struct UserLoginEventLog
{
    public string UserName;
    public DateTime TimeLoggedOn;
    public int UserID;
    public UserLoginEventLog(string userName, DateTime timeLoggedOn, int userID)
    {
        UserName = userName;
        TimeLoggedOn = timeLoggedOn;
        UserID = userID;
    }
}

The security handler subscribes to the event on the ILoginService, and when the event fires, populates the struct with the data from the event.

We add an event to the ILoginService interface and create a custom EventArgs class to pass the data to the listener:

public interface ILoginService
{
    int ValidateUser(string userName, string password);
    event UserLoggedOnEventHandler UserLoggedOnEvent;
}
public delegate void UserLoggedOnEventHandler(
    object sender, UserLoggedOnEventArgs eventArgs);
public class UserLoggedOnEventArgs : EventArgs
{
    public string UserName { get; set; }
    public DateTime TimeLoggedOn { get; set; }
    public int UserID { get; set; }
    public UserLoggedOnEventArgs(
        string userName, DateTime timeLoggedOn, int userID)
    {
        this.UserName = userName;
        this.TimeLoggedOn = timeLoggedOn;
        this.UserID = userID;
    }
}

To validate our system under test, we inject the appropriate mock objects, and arrange the appropriate methods.  We don’t need to arrange the call on the ShoppingCartRepository since we are ok with it returning null (remember, a loose mock returns the default value for any method not arranged).

It is important to call Mock.Raise after the instantiation of the security handler since the even is subscribed to in the constructor. Otherwise we get a null reference exception. We can then verify that the event was raised, and our code correctly assigns the values from the event args into the struct representing our event log.

public void Should_Raise_Event_When_Executed()
{
    int userID = 5;
    string userName = "Bob";
    string password = "pwd";
    DateTime timeLoggedOn = DateTime.Now;
    ILoginService service = Mock.Create<ILoginService>();
    IShoppingCartRepository cartRepo = Mock.Create<IShoppingCartRepository>();
    Mock.Arrange(() => service.ValidateUser(userName, password))
        .IgnoreArguments().Returns(5);
    SecurityHandler handler = new SecurityHandler(service, cartRepo);
    Mock.Raise(() => service.UserLoggedOnEvent += null, 
        new UserLoggedOnEventArgs(userName, timeLoggedOn, userID));
    handler.LoginUser(userName, password);
    Assert.That(handler.EventLog.UserID, Is.EqualTo(userID));
    Assert.That(handler.EventLog.TimeLoggedOn, Is.EqualTo(timeLoggedOn));
    Assert.That(handler.EventLog.UserName, Is.EqualTo(userName));
}

The astute reader will notice that we can comment out the call to handler.LoginUser(userName, password) and the test will still pass.  That is because the call to Mock.Raise unconditionally raises the event.

Fortunately, JustMock provides another version, the Raises() extension method on the Arrange.  This allows you to specify an event that is raised after a method is called. The significant change in the following test is highlighted in yellow.  The call to Mock.Raise is removed, and is moved to the Arrange code block.  This tells the JustMock framework to raise the event after the ValidateUser method is called, which is the expected behavior of our line of business code.

[Test]
public void Should_Raise_Event_When_Executed()
{
    int userID = 5;
    string userName = "Bob";
    string password = "pwd";
    DateTime timeLoggedOn = DateTime.Now;
    ILoginService service = Mock.Create<ILoginService>();
    IShoppingCartRepository cartRepo = Mock.Create<IShoppingCartRepository>();
    Mock.Arrange(() => service.ValidateUser(userName, password))
        .IgnoreArguments()
        .Raises(() => service.UserLoggedOnEvent += null, 
        new UserLoggedOnEventArgs(userName, timeLoggedOn, userID))
        .Returns(5);
    SecurityHandler handler = new SecurityHandler(service, cartRepo);
    handler.LoginUser(userName, password);
    Assert.That(handler.EventLog.UserID, Is.EqualTo(userID));
    Assert.That(handler.EventLog.TimeLoggedOn, Is.EqualTo(timeLoggedOn));
    Assert.That(handler.EventLog.UserName, Is.EqualTo(userName));
}

Summary

Handing exceptions and events are typically difficult areas of code to test.  JustMock again makes it very routine to accomplish these tasks with minimal code.

Happy Coding!

Comments are closed
Managed Windows Shared Hosting by OrcsWeb