Wednesday, April 7, 2010

Unit testing, how do I unit test WebService X with dependency Y?

Before moving on, I would like to build on our WebService example. In that example, we separated our business logic from our hosting solution. However, it may occur that our source solution has dependencies that must be fulfilled.

Consider this modified example,

// a profile web service, represents business logic 
// hosted from a web service
public class ProfileWebSevice : System.Web.Services.WebService
{

    // gets a profile for remote client
    [WebMethod]
    public Profile GetProfile (Session session)
    {
        Profile profile = null;

        // obtain authentication service from
        // web service context
        AuthenticationService auth = 
            (AuthenticationService)(Context.Cache["auth"]);

        // embedded business logic, bad bad bad
        // 
        // 1. authenticate
        if (auth.IsAuthenticated (session))
        {
            // 2. create sql connection
            // 3. get profile
            // 4. populate profile
            profile = new Profile (dataReader);
        }

        return profile;
    }

}

Note the dependency introduced by member variable Context. If we were to perform our simple refactor, we introduce compile-time issues,

public class ProfileService  
{ 
    public Profile GetProfile (Session session) 
    { 
        // COMPILE-ERROR: "Context" does not exist here! oh nos!!!
        AuthenticationService auth = 
            (AuthenticationService)(Context.Cache["auth"]);
        // ...
    } 
}

Fortunately, the solution is fairly straightforward. The key is realising our business logic of authenticate, fetch, instantiate is completely separate from the services that facilitate it. Our business class is a consumer of these services. From this perspective, it seems obvious to request these services as part of our invocation.

public class ProfileService  
{ 
    public Profile GetProfile (AuthenticationService auth, Session session) 
    {
        if (auth.IsAuthenticated (session)) { ... }
        // ...
    }
}

and our WebService now looks like

public class ProfileWebSevice : System.Web.Services.WebService
{
    [WebMethod]
    public Profile GetProfile (Session session)
    {
        AuthenticationService auth = 
            (AuthenticationService)(Context.Cache["auth"]);
        ProfileService service = new ProfileService ();
        Profile profile = service.GetProfile (auth, session);
        return profile;
    }
}

Again, our business logic does not care who or how a service is implemented, only that whomever is doing it, conforms to some known invocation. In this scenario, that an object instance of type AuthenticationService contains a method IsAuthenticated.

Our unit tests may look like

// test business logic without web service! yay! 
[TestMethod] 
public void Test_GetProfile_NullSession () 
{ 
    AuthenticationService auth = new AuthenticationService ();
    ProfileService service = new ProfileService (); 
    Profile actual = service.GetProfile (auth, null);
    // verify profile expectations
    // verify authentication service expectations
}