2008-02-14

ECO, should we mock it?

I watched a video on Rhino Mocks yesterday. What a great framework! Obviously I wanted to know if I could use this with ECO so I thought I'd give it a try.

In my website's AccountController there is a method like so

public void AttemptLogin(string emailAddress, string password, string redirectUrl)
{
}


Now I could just go ahead and write some OCL to find the user, but instead of doing this I really want to separate the code a bit. So I created a class

public class UserRepository
{
  private readonly IEcoServiceProvider ServiceProvider;

  public UserRepository(IEcoServiceProvider serviceProvider)
  {
    ServiceProvider = serviceProvider;
  }

  public User GetByEmailAddressAndPassword(string emailAddress, string password)
  {
    string searchEmail = BusinessClassesHelper.EscapeOcl(emailAddress);
    string criteria = string.Format("->select(emailAddress.sqlLikeCaseInsensitive('{0}'))", searchEmail);
    return BusinessClassesHelper.SelectFirstObject<User>(ServiceProvider, "User", criteria);
  }
}


Now I can get my user like so....

public void AttemptLogin(string emailAddress, string password, string redirectUrl)
{
  MyWebsiteEcoSpace ecoSpace = new MyWebsiteEcoSpace();
  ecoSpace.Active = true;
  try
  {
    UserRepository repository = new UserRepository(ecoSpace);
    MyWebsite.Model.User user = repository.GetByEmailAddressAndPassword(emailAddress, password);
  }
  finally
  {
    ecoSpace.Active = false;
  }
}


So what's the benefit? The important thing to note is that I pass in an instance of IEcoServiceProvider to the UserRepository object. So if I want to test the UserRepository class on its own I can pass a dummy object for the serviceProvider. This means that I don't have to access the DB which would slow things down (especially if I have to keep clearing the DB down), in fact I don't even need to connect to the DB at all!

If you remember UserRepository.GetByEmailAddressAndPassword() looks like this
public User GetByEmailAddressAndPassword(string emailAddress, string password)
{
  string searchEmail = BusinessClassesHelper.EscapeOcl(emailAddress);
  string criteria = string.Format("->select(emailAddress.sqlLikeCaseInsensitive('{0}'))", searchEmail);
 return BusinessClassesHelper.SelectFirstObject(ServiceProvider, "User", criteria);
}


and BusinessClassesHelper uses the IOclPsService and IOclService in combination to get to the result. Surely this is all too complicated to mock? Not with Rhino, no!

[TestFixture]
public class UserRepositoryTests
{
  MockRepository Mocks;
  IEcoServiceProvider MockServiceProvider;
  IOclPsService MockOclPsService;
  MyWebsiteEcoSpace EcoSpace;

  [SetUp]
  public void SetUp()
  {
    //Create a mock repository
    Mocks = new MockRepository();

    //Create the mock IEcoServiceProvider
    MockServiceProvider = Mocks.CreateMock<IEcoServiceProvider>();

    //I also need a mock IOclPsService to avoid DB access
    MockOclPsService = Mocks.CreateMock<IOclPsService>();

    //Create a transient version of my EcoSpace
    EcoSpace = new MyWebsiteEcoSpace ();
    EcoSpace.PersistenceMapper = null; //No persistence!
    EcoSpace.Active = true;
  }

  [TearDown]
  public void TearDown()
  {
    EcoSpace.Active = false;
    Mocks.ReplayAll(); //Just in case we forgot, calling twice has no effect!
    Mocks.VerifyAll(); //Ensure everything expected was called
  }

  [Test]
  public void GetUserByEmailAddressAndPassword()
  {
    //Create a list of users to return from the mock IOclPsService
    IObjectList userList = EcoSpace.VariableFactory.CreateTypedObjectList(typeof(User), false);
    
    //Add a single user to that list
    User expectedUser = new User(EcoSpace);
    expectedUser.EmailAddress = "me@home.com";
    expectedUser.SetPassword("1234567890");
    userList.Add(expectedUser.AsIObject());

    //Start specifying what we expect to be called, and what we should do as a result
    Mocks.Record();

    //When GetEcoService<IOclPsService> is called return our MockOclPsService
    Expect.Call(MockServiceProvider.GetEcoService<IOclPsService>()).Return(MockOclPsService);
  
    //Same for GetEcoService(typeof(IOclPsService))
    Expect.Call(MockServiceProvider.GetEcoService(typeof(IOclPsService))).Return(MockOclPsService);

    //When asked for the IOclService (not PS service) return the real one
    Expect.Call(MockServiceProvider.GetEcoService<IOclService>()).Return(EcoSpace.Ocl);
    Expect.Call(MockServiceProvider.GetEcoService(typeof(IOclService))).Return(EcoSpace.Ocl);

    //When MockOclPsService.Execute is executed return our userList
    Expect.Call(MockOclPsService.Execute(null)).Return(userList);
    //This means we don't care what the exact parameter is, any OCL will do
    LastCall.IgnoreArguments();

    //Now go into play back mode
    Mocks.ReplayAll();

    //Create the UserRepository using our mock services
    UserRepository repository = new UserRepository(MockServiceProvider);

    //Ask for the user
    User foundUser = repository.GetByEmailAddressAndPassword(expectedUser.EmailAddress, "1234567890");

    //Ensure that we got the same user back
    Assert.AreEqual(expectedUser, foundUser, "Found the wrong user");
  }
}



Nice eh :-)

1 comment:

Anonymous said...

You will still have a problem when testing AttemptLogin.
Instead of going around in circles, you can use Typemock and mock the OCL directly. You don't have to change your design just to test your code.

[Test,VerifyMocks]
public void GetUserByEmailAddressAndPassword()
{
using (RecordExpectations r = new RecordExpectations())
{
MyWebsiteEcoSpace mockedSpace = new MyWebsiteEcoSpace(); // the actual next instance will be mocked
mockedSpace.GetEcoService(IOclPsService)().Execute(null);
r.Return(userList);
}

//Ask for the user
User foundUser = repository.GetByEmailAddressAndPassword(expectedUser.EmailAddress, "1234567890");

//Ensure that we got the same user back
Assert.AreEqual(expectedUser, foundUser, "Found the wrong user");
}