Chris Vincent
Chris Vincent

Reputation: 258

Mocking methods of the object under test

Lately I've been writing some objects wherein the behavior of one method sometimes consists of calling another one of its own methods under certain conditions. To test this, I've been mocking the methods the object calls on itself as it makes it easier to cover each branch in the code without combinatorial explosion. This is generally considered bad practice. But here's an example which almost exactly mirrors a piece of code I'm working on now (written in CoffeeScript but this philosophical issue has come up for me before in other languages too).

class UserDataFetcher
  constructor: (@dataSource) ->

  fetchUsernameAsync: ->
    # ... snip ... Hits API and raises any errors

  fetchUserCommentsAsync: ->
    # ... snip ... Hits API and raises any errors

  fetchUsernameAndUserCommentsAsync: ->
    # ... snip ... Calls each of the above in order and handles errors specially

This is pretty straightforward. fetchUsernameAsync and fetchUserCommentsAsync each interact with the given @dataSource to fetch the data. Then we have fetchUsernameAndUserCommentsAsync which is essentially a composed method calling the other two and handling them in a certain way; in this example, it might want to re-raise any errors resulting from fetchUsernameAsync but swallow any errors raised by fetchUserCommentsAsync.

Testing the behavior of the first two methods is trivial; I mock out their respective calls to @dataSource and assert that they return the data in the correct format or allow any errors to be raised. Testing the behavior of the last method is where I have my dilemma.

It's still a simple method with minimal branching logic and delegating most of the work by passing messages. Normally I would test its behavior by mocking out its "collaborators" and asserting on certain messages being passed and returning the data in the appropriate format or failing correctly. But the difference here is that it has just one "collaborator", this. I would be mocking some methods of the object under test, which is considered bad practice.

Obviously a way around this would be to move this method to a different object; it would be an object with exactly one method and almost exactly the same mechanics, and it would allow me to mock out its collaborator without violating the "don't mock methods on the object under test" rule. It would probably have a ridiculous name like UsernameAndCommentsFetcher, and I would go from having one small class to two tiny, almost microscopic classes. Small classes are good, but this level of granularity might be a little overkill, and in this case would only exist to satisfy the "don't mock methods on the object under test" rule.

Is this a solid rule or are there cases, such as this, where it's reasonable to bend the rule?

(Note: I realize this is similar to this question, but I think the example is different enough to warrant another look: As a "mockist" TDD practitioner, should I mock other methods in the same class as the method under test?)

Upvotes: 3

Views: 626

Answers (1)

Andy Waite
Andy Waite

Reputation: 11106

Very few rules in programming are 100% solid.

But in this case, could you extract the mock setup code for fetchUsernameAsync and fetchUserCommentsAsync into setup methods, and then call both of those in fetchUsernameAndUserCommentsAsync? e.g. (in RSpec-like syntax):

specify "fetchUsernameAsync" do
  setup_username_mock
  expect(...).to eq ...
end

specify "fetchCommentsAsync" do
  setup_comments_mock
  expect(...).to eq ...
end

specify "fetchUsernameAndCommentsAsync" do
  setup_username_mock
  setup_comments_mock
  expect(...).to eq ...
end

There would be no need for the fetchUsernameAndCommentsAsync tests to cover all paths already covered by the previous specs.

Upvotes: 1

Related Questions