Unit Testing & Fake Repository implementation with cascading CRUD operations
i'm having trouble writing integration tests which use a fake repository,
For example : Suppose I have a classroom entity, which aggregates students...
var classroom = new Classroom();
classroom.Students.Add(new Student("Adam"));
_fakeRepository.Save(classroom);
_fakeRepostiory.GetAll<Student>().Where((student) => student.Name == "Adam"));
// This query will return null...
When using my real implementation for repository (NHibernate based), the above code works (because the save operation would cascade to the student added at the 开发者_开发问答previous line),
Do you know of any fake repository implementation which support this behaviour? Ideas on how to implement one myself?
Or do you have any other suggestions which could help me avoid this issue?
Thanks in advance, Erik.
If you are writing "integration test" as the body of your question indicates, you wouldn't use a fake implementation -- use the real thing.
If you are writing unit tests, as the title of your question indicates, you wouldn't care whether the operation cascades, because that is not a concern of the Classroom class -- all you care about is that Save()
gets called on whatever repository the class was given as a dependency.
If you are writing a unit test around NHibernate's ability to perform cascade operations, those have already been written and prove nothing about the Classroom class.
Edit in response to comment:
Unit tests are used to test individual "units" of functionality, independent of any other classes or services -- in this case, you use fakes for those other classes to ensure that they do exactly and only what this class needs. Integration tests are used to test the interaction among multiple classes and/or subsystems (like database access, ORM, etc.). In this case, it seems like you want to test the cascading save/update, but that is the responsibility of NHibernate here, isn't it?
But let's say you have a class that saves classroom
using an IRepository<Classroom>
, and it wraps that action in a try…catch
block, and you want to unit test that when the IRepository<Classroom>
throws a RepositoryWriteFailureException
, an error count property, ErrorCount
gets incremented. Here you do need a fake implementation of your repository, and this is where a mocking framework like RhinoMocks, nMock, Moq, or TypeMock Isolator come into play -- these can generate the fake for you, using an interface, abstract class, or a class whose public methods and properties have been declared virtual.
I might write the body of a test like this (treat this as pseudo-code):
// set up the context (arrange)
// create the fake repository
var fake_repository = MockRepository.GenerateStub<IRepository<Classroom>>();
// create the classroom, injecting the fake repository
dim classroom as new Classroom(fake_repository);
// now tell the fake how to behave
fake_repository.Stub(repo => repo.Save(classroom))
.WhenCalled(throw new RepositoryWriteFailureException());
// do what you're testing (act)
classroom.Save();
// assert the expected behaviour (assert)
// verify that the fake_repository was told to Save(classroom)
Assert.IsTrue(fake_repository.WasToldTo(repo => repo.Save(classroom)).Times(1);
// verify that the error count property was incremented
Assert.IsTrue(classroom.ErrorCount == 1);
Notice how we have tested how the Classroom
class, or whatever you're testing, behaves without even having to implement the repository.
Since it is an integration test, I'd recommend you to use the real repository, and not just a fake one.
A good solution would be to create a separate database for testing purposes and set NHibernate's hbm2ddl.auto
property to create
in the test project's config file, that way it would re-create the database every time, so you wouldn't have to worry about that eitther.
I use exactly this for testing purposes. I use FluentNHibernate's auto mapper with custom conventions. You may also want to use SQLite if you'd like to use something lighter that SQL Server for testing.
(I unit test my repository this way, using SQL Server.)
You can also set it to update
, too, if you want it to preserve data you already entered to it, and you shouldn't set it at all if you're okay with manually creating a testing database.
+1 for not calling it «integration test» if the test doesn't use the real thing (i.e. isn't testing the integration with the RDBMS).
If you want cascading and all the other goodness of NHibernate and the speed of «unit test»s, I've found using an in-memory database (SQLite) extremely useful.
精彩评论