In my question As a “mockist” TDD practitioner, should I mock other methods in the same class as the method under test?, Avdi answered ‘Personally I think that mocking on self is almost always a code smell. It’s testing the implementation rather than the behavior.’ He may be right, but often I can’t distinguish between the implementation and the behavior.
I have another example (in Python-style pseudo-code) that may lead to helpful answers:
class Consumer: def spec_dirpath: client = VCS.get_connection(self.vcs_client_name) client.sync() return client.dirpath() def spec_filepath: filepath = os.path.join(spec_dirpath(), self.spec_filename) if not os.path.exists(filepath): raise ConsumerException return filepath def get_components: return Components.get_components_from_spec_file(self.spec_filepath())
The idea here is that the get_components method calls the spec_filepath method in order to get a path to a file that the get_components_from_spec_file Components class method will read a list of components from. The spec_filepath method in turn calls spec_dirpath, which syncs the directory containing the spec file from the VCS system and returns the path to that directory. (Try not to look for bugs in this code–it’s pseudo-code, after all.)
I’m looking for advice on how to test these methods…
Testing spec_dirpath should be quite straightforward. I can mock the VCS class and have it return a mock object and confirm the appropriate methods are called (and that the spec_dirpath method returns what the mock’s dirpath method returns).
But if I don’t mock spec_dirpath while testing spec_filepath, how do I avoid duplicating the same test code from the spec_dirpath code in the spec_filepath test? And if I don’t mock spec_filepath while testing get_components, how do I avoid duplicating the test code from both spec_filepath and spec_dirpath?
Unit testing works better with some form of dependency injection. In this case because you are creating the Client in the code you create a dependency in your test that requires some form of dynamic mock to cope with. (We’d use a partial mock in this case to mock our calls that create dependencies to avoid having to test those dependencies as well).
If you inject the dependency on startup (i.e. Pass in a client object) then you can mock it easily and not have major difficulties testing just the one class.
So you need either a partial mock or a dependency injection solution to meet those objectives.