views:

243

answers:

3

I've created a few small fluent interfaces through method chaining. They typically call a number of Repositories that fetch data from webservices / databases.

How should I go about unit testing methods that use the fluent interface?

Public IEnumberable<Computer> FindComputers(string serialNumber)
{
      return Computers.FindBySerialNumber("YBCX00900")
         .AttachConfiguration()
         .EnsureAllComputersHaveConfiguration();
}

I can unit test the individual components of the fluent interface, but if I want to unit test the FindComputers method above what should I do?

  1. Use the concrete implementation of the fluent interface, and write expectations on the Repository classes
  2. Mock the fluent interface itself and set expectations on that
  3. Test only the fluent interface itself, and not the FindComputers() method

I'd like to find an easily maintainable approach.

A: 

I would do 2+3. Assuming the fluent interfaces are truely interfaces they should be relatively simple to mock. Just realize each step in the call chain should probably return a new mock object, that is in turn expecting the next call in the chain.

You still should test the fluent interface directly though, mocking the repository layer beneath them.

Frank Schwieterman
Thanks for the input,I thought about mocking the fluent interface itself, but it just seems odd to write tests like... Expect(x => x.FindBySerialNumber(null)).Return(nextMock) Expect(x => x.AttachConfiguration()).Return(nextMock)when all it tests is that the calls are actually made. It's a lot of work to recreate the whole fluent interface in mock objects, only to test what is already clearly written in the method under test.
Andronicus
I'm new to doing unit test coverage, and have the same qualms at times. While its easy to review the code by inspection, thats still manual review. Adding test coverage helps protect you down the road when you're no longer thinking about this class. This may also be an indication that the fluent interface is more complicated than it needs to be- if the interface were more focused on what you need the tests would be simpler to write as well.
Frank Schwieterman
+1  A: 

Can you mock your Repositories? While some will advocate a more pure approach where you must isolate one method of one class, it would be a decent way to test how FindComputers and the fluent interface work together. And it may be simpler, depending on what the Repository access layer looks like.

Neil Whitaker
+1  A: 

I think that the FI is doing more than it needs to. I assume you are using Computers as a data mapper and also use it for building a query. From what you have shown the query is built up from this:

rule 1: find configured computer with serial number = "whatever" and has-config = true.
rule 2: find not-config computer with serial number = "whatever and has-config = true.
rule 3: find configured computer with serial number = "whatever" and has-config = false.
rule 4: find not-config computer with serial number = "whatever" and has-config = false.
rule 5: find all computer with serial number = "whatever" and has-config = true.
rule 6: find all computer with serial number = "whatever" and has-config = false.

and so on...

Now some of these rules that can be implemented seem to be incorrect. rule 2 and rule 3 seem to be at cross purposes. rule 5 and rule 6 does what? Is this about right?

Because you've implemented an object that breaks SRP. The first step is to break the query builder from the data mapper. Build your FI query object then pass it into the mapper.

Now you can test FindComputers making sure that FI query object is sent to a data mapper. Because you now can build a FI query object you can test it. And you can test that the data mapper uses a query object.

What if in the future you want to find computers by location. If you keep the same code as you wrote you would have to add a method FindByLocation and before you know it you have a god object. smelly!

Gutzofter
Thanks, you're right the example is poorly thought out, I've since broken up the FI into one for the query, and one to perform operations on the data returned.I've found it easiest to unit test the FI in isolation, and then unit test methods that use the FI with the concrete implementation. Just testing that the desired result is returned.Trying to mock the FI just makes the tests too brittle.
Andronicus