I came across an interesting issue tonight exploring the use of Rhino.Mocks in unit tests. I have a class UserRepository that implements my current understanding of the Repository Pattern. My repository uses the interface IDataProvider to persist my User entity. The concrete class UserDataProvider wraps NHibernate.

My goal was to write unit tests that cover the methods of UserRepository. I used Rhino.Mocks to mock away UserDataProvider to prevent actually needing a database.

Things were going fine:

public void SaveTest(){
            MockRepository mock = new MockRepository();

            IDataProvider dataProvider = 
                  (IDataProvider) mock.CreateMock<IDataProvider>();
            UserRepository target = new UserRepository(dataProvider);
            User u = new User();
            
            Expect.Call(dataProvider.Save(u)).Return(1);
            Expect.Call(dataProvider.GetById(1)).Return(u);
            
            mock.ReplayAll();
            User x = target.Save(u);
            mock.VerifyAll();

            Assert.AreEqual(x, u);
        }
 
public void GetListTest(){
            MockRepository mock = new MockRepository();
            IDataProvider dataProvider = 
                 (IDataProvider)mock.CreateMock<IDataProvider>();
            UserRepository target = new UserRepository(dataProvider);

            Expect.Call(dataProvider.GetList()).Return(
                new List<object> { new User(), new User()});

            mock.ReplayAll();
            IList<User> users = target.GetList();
            mock.VerifyAll();
            Assert.AreEqual(2, users.Count);  
        }

Then I hit a snag. My User entity has a property Id that is read only. The users Id comes form the database and should not be able to be modified. So my next test has a bit of a problem and fails to compile.

public void GetByIdTest() {
            MockRepository mock = new MockRepository();

            IDataProvider dataProvider = (IDataProvider)mock.CreateMock<IDataProvider>();
            UserRepository target = new UserRepository(dataProvider);

            //TODO: Interesting Problem here. My actual dataProvider uses NHib
            //and would be able to meet this expectation. But my mock
            //cannot set the read only id property so this test will not
            //even compile.
            Expect.Call(dataProvider.GetById(1)).Return(new User() { Id = 1 });

            mock.ReplayAll();
            User u = target.GetById(1);
            mock.VerifyAll();

            Assert.AreEqual(1, u.Id);
        }

 

NHibernate has no problem injecting the value when I fetch the object from it, but my simple little expectation above chokes on it. I am not sure how to get around this one with out violating the intent of the test. I'll have to dig into the Rhino.Mocks documentation and see what tools it offers to get around this situation.

So far Rhino.Mocks is a great time saver. If I actually had to generate a fake for IDataProvider it would take quite a bit of time. I'll have to work it into my regular work life and see how it fairs there.

I'll update this post when I find a resolution to the issue at hand.


 
Wednesday, July 23, 2008 8:16:55 AM (Pacific Standard Time, UTC-08:00)
I think you're compicating the tests by mixing interaction- and state-based tests unnecessarily. I will say that I'm a big fan of mocking, but here's what I'd do...

IMHO, that last test doesn't need to care what the Id of the User instance is - it looks like an interaction test: you're saying you expect the repository to call the data provider. Testing the user id isn't necessary as its just a place holder - you're actually testing the test code here.

I would create a mock User, then change the expectation to "Expect.Call( dataProvider.GetById(1) ).Return( mockUser );". Next, I'd remove the Assert.AreEqual(...) and replace the "User u = target.GetById(1);" with "Assert.That( target.GetById(1), Is.EqualTo( mockUser ) );" just to ensure that whatever the data provider does return is correctly passed up to the caller.

Same thing for the second test: replace the list with a mock of IList<User>, and use something like "Assert.That( target.GetList(), Is.EqualTo( mockUserList ) );" - again, it's contents aren't important in that test.

My 2 cents. Hope it's helpful :)
Comments are closed.