I thought some more about the syntax of my Rhino Mock DSL. It can feel unnatural putting all the mock expectation code before the call to the object being tested. I came up with this working prototype instead:
[Test]
def Get_data_objects_for_nonexistent_company_throws():
with_mocks:
database = mocks.CreateMock[of IDatabase]()
userProvider = StubUserProvider("andrew", "bad corp")
userProvider.MakeCurrent()
execute:
uds = UserDataService(database, userProvider)
expect_throw FaultException[of GetDataObjectsFault]:
uds.GetDataObjects()
assert thrown_exception.Detail.Type.Equals(GetDataObjectsFaultType.InvalidCompany)
assuming:
database.GetUserID("andrew", "bad corp")
returned 1
assuming:
database.GetCompanyID("bad corp")
returned 0 # returning 0 from database implies nonexistent company.
The with_mocks method sets up the mock repository and a Store object. The execute and assumption methods then put their blocks into the Store. At the end of with_mocks I iterate through assumptions calling each to set up the Rhino Mock expectations. Following that is: mocks.ReplayAll(), call to the "execute" block, then mocks.VerifyAll().
The other clever bit in there is the expect_throw method. This runs the block inside a try...except and fails if no exception (or wrong exception type) is thrown. It puts the exception object into field that is readable using thrown_exception. This means we can then test assertions about the exception contents. I had to cheat a bit and declare thrown_exception as "duck" in Boo i.e. it is late bound. This is so we can access members on the actual object despite not really knowing about it at compile time.
I like the readability now. The outline is:
- Initialize mocks and data objects
- Call the object being tested
- Assert about the result
- State the assumptions about how dependencies are used
The key bit, I feel, is that the call to the object being tested is not buried down at the bottom of the method.
How does everyone else feel about this modified approach?