10

Sometimes objects just need to be tightly coupled. For example, a CsvFile class will probably need to work tightly with the CsvRecord class (or ICsvRecord interface).

However from what I learned in the past, one of test-driven development's main tenets is "Never test more than one class at a time." Meaning you should use ICsvRecord mocks or stubs rather than actual instances of CsvRecord.

However after trying this approach, I noticed that mocking out the CsvRecord class can get a little hairy. Which leads me to one of two conclusions:

  1. It's hard to write unit tests! That's a code smell! Refactor!
  2. Mocking out every single dependency is just unreasonable.

When I replaced my mocks with actual CsvRecord instances, things went much more smoothly. When looking around for other peoples' thoughts I stumbled across this blog post, which seems to support #2 above. For objects that are naturally tightly coupled, we should not worry so much about mocking.

Am I way off track? Are there any downsides to assumption #2 above? Should I actually be thinking about refactoring my design?

Phil
  • 3,680
  • 28
  • 30

5 Answers5

11

If you really need coordination between those two classes, write a CsvCoordinator class that encapsulates your two classes, and test that.

However, I dispute the notion that CsvRecord is not independently testable. CsvRecord is basically a DTO class, is it not? It's just a collection of fields, with maybe a couple of helper methods. And CsvRecord can be used in other contexts besides CsvFile; you can have a collection or array of CsvRecords, for example.

Test CsvRecord first. Make sure that it passes all of its tests. Then, go ahead and use CsvRecord with your CsvFile class during test. Use it as a pre-tested stub/mock; fill it with relevant test data, pass it to CsvFile, and write your test cases against that.

Robert Harvey
  • 200,592
5

The reason for testing one class at a time is that you don't want tests for one class to have dependencies on the behaviour of a second class. That means that if your test for Class A exercises any of the functionality of Class B, then you should mock Class B to remove the dependency on particular functionality within Class B.

A class like CsvRecord seems to me that it's mostly for storing data - it's not a class with too much functionality of its own. That is, it may have constructors, getters, setters, but no methods with real substantial logic. Of course, I'm guessing here - maybe you've written a class called CsvRecord that does numerous complex calculations.

But if CsvRecord doesn't have real logic of its own, there's nothing to be gained by mocking it. This is really just the old maxim - "don't mock value objects" .

So when considering whether to mock a particular class (for a test of a different class), you should take into account how much of its own logic that class has, and how much of that logic will be executed in the course of your test.

1

No. #2 is fine. Things can be, and should be tightly coupled if their concepts are tightly coupled. This should be rare, and generally avoided, but in the example you provided it makes sense.

Telastyn
  • 110,259
0

"Coupled" classes are mutually dependent on one another. This should not be the case in what you are describing--a CsvRecord should not really care about the CsvFile containing it, so the dependency only goes one way. That's fine, and is not tight coupling.

After all, if a class contains the variable String name, you would not claim it is tightly coupled to String, would you?

So, unit test the CsvRecord for its desired behavior.

Then use a mocking framework (Mockito is great) to test if your unit is interacting with the objects it depends on correctly. The behavior you want to test, really--is the CsvFile handles CsvRcords in the expected fashion. The inner workings of CvsRecord shouldn't matter--it's how CvsFile communicates with it.

Finally, TDD is not only about unit tests. You certainly can (and should) start with functional tests that look at the functional behavior of how your larger components work--that is, your user story or scenario. Your units tests set the expectations and verify the pieces, the functional tests do the same for the whole.

Matthew Flynn
  • 13,495
  • 2
  • 39
  • 58
0

There are really two questions here. The first is if situations exist where mocking an object is inadvisable. That's undoubtedly true, as shown by the other excellent answers. The second question is whether your particular case is one of those situations. On that question I am not convinced.

Probably the most common reason not to mock a class is if it's a value class. However, you have to look at the reason behind the rule. It's not because the mocked class will be bad somehow, it's because it will be essentially identical to the original. If that were the case, your unit testing would not be any easier using the original class.

It very well may be that your code is one of the rare exceptions where refactoring wouldn't help, but you should only declare it such after diligent refactoring efforts haven't worked. Even seasoned developers can have trouble seeing alternatives to their own design. If you can't think of any possible way to improve it, ask someone experienced to give it a second look.

Most people seem to be assuming your CsvRecord is a value class. Try making it one. Make it immutable if you can. If you have two objects with pointers to each other, remove one of them and figure out how to make it work. Look for places to split classes and functions. The best place to split a class may not always match up with the physical layout of the file. Try reversing the parent/child relationship of the classes. Maybe you need a separate class for reading and writing csv files. Maybe you need separate classes for handling the file I/O and the interface to the upper layers. There are lots of things to try before declaring it unrefactorable.

Karl Bielefeldt
  • 148,830