JustMock has long supported testing events through the Raises() method (for more information see this blog post).  I am proud to announce that we have just added support for Raises in our newest feature, Automocking.

The System Under Test

A common security requirement is to track invalid login attempts. To meet this requirement and make sure the system remains flexible, the service used to validate a users’ credentials (IUserValidationService) will raise an event when a login attempt fails.  Classes that use the validation service can choose whether or not to listen for the event as well as how to respond if the event gets raised. 

The System Under Test in this example is a simple Login class that has two dependencies, the user validation service mentioned above as well as a simple logging service. 

The user validation service interface is shown in Listing 1.

public interface IUserValidationService
{
    event EventHandler<LoginEventArgs> InvalidLoginAttemped;
 int ValidateUser(string userName, string password);
}

Listing 1 – User Validation Service

The InvalidLoginAttempted event uses custom EventArgs to pass the username, password, and the error message to the subscribers.  The custom event args are shown in Listing 2.

public class LoginEventArgs : EventArgs
{
 public string UserName { get; private set; }
 public string Password { get; private set; }
 public string Message { get; set; }
 public LoginEventArgs(string userName, string password, string message)
    {
        UserName = userName;
        Password = password;
        Message = message;
    }
}
Listing 2 – Custom Event Args

For logging, we introduce a very simple interface, as shown in Listing 3.

public interface ILoggingService
{
 void LogInvalidLogin(string userName, string password, string message);
}
Listing 3 – Logging Service

The login class is very straightforward.  The validation service and the logging service are passed in as dependencies.  The event handler for the InvalidLoginAttempted event logs the details using the logging service.  The entire Login class is shown in Listing 4.

public class Login
{
 private readonly ILoggingService _logger;
 private readonly IUserValidationService _userService;
 
 public Login(IUserValidationService userService, ILoggingService logger)
    {
 this._userService = userService;
 this._logger = logger;
        _userService.InvalidLoginAttemped += UserServiceInvalidLoginAttemped;
    }
 
 public bool LoginUser(string userName, string password)
    {
 int userID = _userService.ValidateUser(userName, password);
 return userID == 0;
    }
 
 void UserServiceInvalidLoginAttemped(object sender, LoginEventArgs e)
    {
        _logger.LogInvalidLogin(e.UserName, e.Password, e.Message);
    }
}

Listing 4 – The Login Class

Testing The Behaviors

There are two main types of Unit Tests, tests that test the state of the system under test and tests that test the behavior of the system under tests and its dependencies.  The cleanest way to test events is through behavior testing, and fortunately JustMock makes that very easy.

Setting up the Basic Test

The first step is to create the basic framework for the unit test, using JustMock automocking.  After arranging the variables needed for the test, we create an instance of the Login class through a call to MockingContainer<Login>().  As a review, this creates mocks for the constructor dependencies automatically.  This initial set up is shown in Listing 5.

[Test]
public void ShouldRaiseEvent()
{
    string userName = "Bob";
    string password = "password";
    string message = "Bad login attempt";
 int userID = 0;
 var container = new MockingContainer<Login>();
}

Listing 5 – Initial Setup for Unit Test

We still have to arrange the mocks that either affect the test or represent behaviors that we want to assert. As a reminder, one of the advantages of automocking is that dependencies that don’t affect the test can be ignored.  In this test, the first method that we need to arrange is the call to ValidateUser on the user validation service.  For now, we only set up the basic arrangement, as we will set up the raising of the event later in this post. We also arrange the call to LogInvalidLogin on the logging service so we can verify that it actually gets called.  Verification that a method is called is a two step process.  Step 1 is to add the Occurs(x) to the arrangement, where “x” is the number of times the method must be called.  You can also use the convenience method OccursOnce(), which is the same as Occurs(1).  The second step is to call container.Assert(), which will raise an exception if the arrangements are met.  The code is shown in Listing 6.

container.Arrange<IUserValidationService>(
        x => x.ValidateUser(userName, password))
    .Returns(userID)
    .OccursOnce();
container.Arrange<ILoggingService>(
        x => x.LogInvalidLogin(userName, password, message))
    .Occurs(1);
var loggedIn = container.Instance.LoginUser(userName, password);
container.Assert();
Listing 6 – Basic arrangements on UserValidation and Logging services

Of course, the assertion would fail at this point since we don’t have an action in our test.  With automocking, we access the system under test through the automocking container.  To add the action to our test, insert the code shown in Listing 7.

var loggedIn = container.Instance.LoginUser(userName, password);
container.Assert();
Listing 7 – Calling LoginUser on the System Under Test

You might have noticed at this point that there aren’t any traditional assertions in this test.  Remember that what we want to test is that an event gets fired when there is an invalid login.  Other tests in our test battery will verify that the state of the system is what we expect, but it’s important to keep your tests laser focused or they can become brittle.

The last (and most important) step is to arrange the event on our user validation dependency.  There are two ways to do with, either using the Raise() or Raises() method.  The Raises method is much better for behavior testing, since the raising of the event becomes part of the arrangement, and not just a random event that gets fired when the object is created.  Looking at the code in Listing 8, the arrangement now reads:

  • When ValidateUser is called (with the specific userName and password)
  • Then Raise the InvalidLoginAttempted event (with the specified event args)
  • Finally return the userID to the calling code
  • And it must occur one time and one time only

It’s important to note that we have to call container.Get<IUserValidationService> to get the mock that will raise the event, since the automocking container holds all of the references.

container.Arrange<IUserValidationService>(
                x => x.ValidateUser(userName, password))
           .Raises(() => container.Get<IUserValidationService>()
                   .InvalidLoginAttemped += null, new LoginEventArgs(
                       userName,password,message))
           .Returns(userID)
           .OccursOnce();
Listing 8 – Raising an event as part of the arrangement

The complete test is shown in Listing 9. 

[Test]
public void ShouldRaiseEvent()
{
    string userName = "Bob";
    string password = "password";
    string message = "Bad login attempt";
 int userID = 0;
 var container = new MockingContainer<Login>();
    container.Arrange<IUserValidationService>(
                    x => x.ValidateUser(userName, password))
                .Raises(() => container.Get<IUserValidationService>()
                    .InvalidLoginAttemped += null, new LoginEventArgs(
                            userName,password,message))
                .Returns(userID)
                .OccursOnce();
    container.Arrange<ILoggingService>(
                    x => x.LogInvalidLogin(userName, password, message))
                .Occurs(1);
 var loggedIn = container.Instance.LoginUser(userName, password);
    container.Assert();
}

Listing 9 – The complete unit test

Summary

The biggest difference from raising an event from a traditional mock (as outlined in this post) and an automocked dependency is how you get the reference  to the mock object.  It’s just as easy to verify the behavior of your system under test using automocking as it is with traditional mocking.


Japikse
About the Author

Phil Japikse

is an international speaker, a Microsoft MVP, ASPInsider, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community. Phil has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil also hosts the Hallway Conversations podcast (www.hallwayconversations.com) and serves as the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic, or read his personal blog at www.skimedic.com/blog.

 

Comments

Comments are disabled in preview mode.