mocking

.NET Musings

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

NAVIGATION - SEARCH

Asserting Behavior and Mocking Legacy Code

Note to reader:  I had to update this post.  Turns out I made a mistake in my initial run of these tests, in that I had the JustMock profiler turned on, so I was running the commercial version.  I have split out the tests and the code samples between the free and commercial version so that you can see the difference in how to handle these tasks between the different versions.

One of the biggest challenges I hear from the community is how to do more testing with legacy code.  Michael Feathers refers to any code that isn’t under test as “Legacy Code”, so your version of the Legacy Code might have been written as recently as yesterday!

Look at the following classes.  The LoginService class is used for validating a user.  For this example, I have the ValidateUser method hard coded to return the value 5 so we can track the execution path in our sample tests.  The LegacyCode class instantiates a LoginService in the CheckUser method and calls the ValidateUser method.  If you don’t see why this code is an issue, read this post.

public class LegacyCode
{
    public int CheckUser(string userName, string password)
    {
        var _service = new LoginService();
        return _service.ValidateUser(userName, password);
    }
}
public class LoginService : ILoginService
{
    public int ValidateUser(string userName, string password)
    {
        return 5;
    }
    public event UserLoggedOnEventHandler UserLoggedOnEvent;
}

Traditional testing methods fall short if we want to test the LegacyCode class.  Fortunately, JustMock and JustMock Free Edition each give us a lot of power and flexibility to handle this and other cases like it. 

There are three different ways that we can arrange our mocks to deal with concrete implementations like this (remember, there isn’t any need for this if the code you are testing uses Dependency Injection and interfaces instead of concrete implementations)

  • Call Original
  • Do Instead
  • Do Nothing

JustMock (Commercial Edition)

Call Original

Call Original allows you to create a mock around a concrete object. Not only a concrete object, but a “future object” – one that we don’t inject into our system under test! The regular code is called as if the object wasn’t mocked, but we get the capability to test for the Behavior of our objects.  This gives the developer the best of both worlds!

Take the following test.  We create a mock as we would for a mocked Interface, but use the actual class.  (If there were parameters that need to be passed into the constructor, they get passed into the Create call after the Behavior.)  In the Arrange, we specify CallOriginal() to tell JustMock to pass the call to ValidateUser straight through to the LoginService class.  We also specify MustBeCalled() to set up an expectation that ValidateUser will get called at least once.

[Test]
public void Should_Assert_Behavior_On_Concrete_Class()
{
    string userName = "User";
    string password = "Pwd";
    LoginService service = 
	Mock.Create<LoginService>(Behavior.Strict);
    Mock.Arrange(()=>service.ValidateUser(userName, password))
    	.CallOriginal().MustBeCalled();
    var sut = new LegacyCode();
    sut.CheckUser(userName, password);
    Mock.Assert(service);
}

As you can see, this allows us to test the behavior of our code without doing any heavy lifting.

Do Instead

Unit Tests should cover not only the “happy” (or successful) path, but also the “unhappy” (or unsuccessful) path. When you are dealing with legacy code (as in the example above), this can be difficult.  For example, with the LoginService, you need to know valid and invalid userid/password combinations.  The problem is that your test has become fragile, since it depends on external systems that you might not have any access to.  If the test starts failing, is it because of your code, or did the password change for your test user?

In the following test, we arrange a DoInstead() method.  This tells the JustMock framework to run the lambda expression instead of the code on the concrete class. In this case, it runs the internal method ValidateUser, and returns the userID value passed in on the Arrange.  This allows for the flexibility of mocking an Interface as shown in my previous posts while working in a legacy code base that doesn’t provide for dependency injection.

[Test]
public void Should_Change_Action_On_Concrete_Class()
{
    int userID = 0;
    LoginService service = Mock.Create<LoginService>(Behavior.Strict);
    Mock.Arrange(()=>service.ValidateUser(Arg.AnyString, Arg.AnyString))
    .DoInstead<string, string>(
        (string userName, string password) =>
        {
            userID = ValidateUser(userName, password, 10);
        })
    .Returns((string userName, string password)=>userID)
    .MustBeCalled();
    var sut = new LegacyCode();
    int returnedUserID = sut.CheckUser("foo", "bar");
    Assert.AreEqual(10, returnedUserID);
    Mock.Assert(service);
}
internal int ValidateUser(
	string userName, string password, int userId)
{
    return userId;
}

Do Nothing

For methods that don’t return anything, we can remove them as factors in our tests by adding a DoNothing extension to the Arrange.  Consider the following update to the LoginService code: The UserLoggedOnEvent gets fired after the user is validated (in this case assigned a value of 5).  What if we don’t want the event to get raised?  There are several reasons for this, but most commonly you are concerned that there would be side effects that could occlude the test results.

public class LoginService : ILoginService
{
    public int ValidateUser(string userName, string password)
    {
        int userID = 5;
        OnValidate(userName, password, userID);
        return userID;
    }
    private void OnValidate(
	string userName, string password, int useID)
    {
        if (UserLoggedOnEvent != null)
        {
            UserLoggedOnEvent(this, new UserLoggedOnEventArgs(
		userName, DateTime.Now, userID));
        }
    }
    public event UserLoggedOnEventHandler UserLoggedOnEvent;
}

To remove this method from consideration, simple create an Arrange with the extension DoNothing() as the following test shows.

[Test]
public void Should_Do_Nothing_On_Concrete_Class()
{
    int userID = 0;
    LoginService service = 
	Mock.Create<LoginService>(Behavior.Strict);
    Mock.Arrange(()=>service.ValidateUser(
	Arg.AnyString, Arg.AnyString)).CallOriginal();
    Mock.Arrange(()=>service.OnValidate(
	Arg.AnyString, Arg.AnyString, Arg.AnyInt)).DoNothing();
    var sut = new LegacyCode();
    int returnedUserID = sut.CheckUser("foo", "bar");
    Assert.AreEqual(5, returnedUserID);
    Mock.Assert(service);
}

JustMock (Free Edition)

Without the profiler, there are some restrictions on what can be mocked on a concrete class and how the concrete classes need to be structured.  First and foremost, the mocked object must be passed into the method that uses it.  We also need to mark the arranged methods as virtual.  The changes are minimal, and can be done without wrecking too much havoc on your legacy code.

public class ModifiedLegacyCode
{
    public int CheckUser(
	string userName, string password, LocalLoginService service)
    {
        return service.ValidateUser(userName, password);
    }
}
public class LocalLoginService : ILoginService
{
    public virtual int ValidateUser(
	string userName, string password)
    {
        int userID = 5;
        return userID;
    }
    public event UserLoggedOnEventHandler UserLoggedOnEvent;
}

Call Original

The only difference in this test is that we want to specify the behavior as CallOriginal, and we need to pass in the mock of the concrete object into the call to CheckUser on the legacy code.

[Test]
public void Should_Assert_Behavior_On_Concrete_Class()
{
    string userName = "User";
    string password = "Pwd";
    LocalLoginService service = 
	Mock.Create<LocalLoginService>(Behavior.CallOriginal);
    Mock.Arrange(()=>service.ValidateUser(userName, password))
	.CallOriginal().MustBeCalled();
    var sut = new ModifiedLegacyCode();
    sut.CheckUser(userName, password, service);
    Mock.Assert(service);
}

Do Instead

The same changes need to be applied to our test for DoInstead. 

[Test]
public void Should_Change_Action_On_Concrete_Class()
{
    int userID = 0;
    LocalLoginService service = Mock.Create<LocalLoginService>();
    Mock.Arrange(() => service.ValidateUser(Arg.AnyString, Arg.AnyString))
    .DoInstead<string, string>(
        (string userName, string password) =>
        {
            userID = ValidateUser(userName, password, 10);
        })
    .Returns((string userName, string password) => userID).MustBeCalled();
    var sut = new ModifiedLegacyCode();
    int returnedUserID = sut.CheckUser("foo", "bar",service);
    Assert.AreEqual(10, returnedUserID);
    Mock.Assert(service);
}

Do Nothing

Lastly, we make the same changes for the DoNothing execution. 

[Test]
public void Should_Do_Nothing_On_Concrete_Class()
{
    int userID = 0;
    LocalLoginService service = 
	Mock.Create<LocalLoginService>(Behavior.Strict);
    Mock.Arrange(()=>service.ValidateUser(Arg.AnyString, Arg.AnyString))
	.CallOriginal();
    Mock.Arrange(()=>service.OnValidate(Arg.AnyString, Arg.AnyString, Arg.AnyInt))
	.DoNothing();
    var sut = new LegacyCode();
    int returnedUserID = sut.CheckUser("foo", "bar");
    Assert.AreEqual(5, returnedUserID);
    Mock.Assert(service);
}

Summary

These techniques can help you to effectively test legacy code that doesn’t allow for injecting traditional mocks, or at least not without a lot of refactoring.  Michael Feathers in Working Effectively with Legacy Code discusses creating seems in the code to facilitate cleaving off sections of code for testing.  The problem is ensuring that creating those seems don’t inject defects into your line of business code.  Use these three techniques (CallOriginal, DoInstead, and DoNothing) to create tests around your code before creating those seems and reduce the number of bugs.  After all, we all want to make it on time to Happy Hour!

Happy Coding!

Managed Windows Shared Hosting by OrcsWeb