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
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
Now I can get my user like so....
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
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!
Nice eh :-)
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 :-)
Comments
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");
}