Bojs
Bojs

Reputation: 93

Effects of Mockito Mocks on Unit Tests When Refactoring

If I use a mockito mock of an object being injected into the SUT as an argument, what happens if during refactoring the code is re-organized to call another non-mocked method of that same mock? My tests would fail and I'd have to go back and change my tests and set them up for this new call (the opposite of what I'd want to be doing when refactoring code)

If this is a common occurrence during refactoring, how can using mocks be of any use except for when mocking external, resource-intensive entities (network, db, etc.)?

I'm using mocks to mock out objects that would take hours to set up given my team seems to love monstrously deep aggregate objects.

Upvotes: 1

Views: 643

Answers (2)

Jeff Bowman
Jeff Bowman

Reputation: 95614

You are correct that refactoring will likely break code that depends on mocks. Mockito doesn't know that method foo(int start, int end) and foo(int start) accomplish the same thing, and if you switch between them while refactoring, your Mockito mocks will very likely break. Mockito does provide reasonable defaults for unstubbed calls, like 0, null, or an empty list; however, many refactors will need more-realistic values.

In general, I've heard the tendency of a test or test fixture to fail when the system is behaving correctly called its "brittleness".

Part of this is derived by the choice of mocking framework: Mockito started life as a fork of EasyMock, where EasyMock will fail by default if there are either too many or too few calls, but Mockito will ignore unexpected calls and otherwise provide "nice" default behavior. The other part is determined by how you use the framework, where verifying unnecessary details (unimportant calls or parameters) may make mocks more brittle than they have to be.

Things Mockito is good at mocking:

  • External, resource-intensive dependencies (or their wrappers).
  • Interfaces. Very little can go wrong here.
  • Small API surfaces. If your API surface has one or two methods, it's unlikely to catch the situation you're describing.
  • Collaborators that don't exist yet, even if they have large API surfaces. Temporary brittle tests can be fixed later.

Things Mockito is not good for mocking:

  • Very large API surfaces and DSLs. Imagine implementing a Builder pattern with Mockito! Prefer using the real object, or writing an in-memory fake or other test double.
  • Stateful objects like databases and model objects. Fakes are definitely a better choice here, if the real objects won't do.
  • Concrete classes you don't control. At that point, implementation details like choices of final start to sneak in. This might be a good reason to create a wrapper you do control.
  • Anything with an existing and well-tested implementation. Why go through all that trouble and lose test fidelity when a real implementation is there?

It's worth saying that no test double will be perfectly safe; your system may cache, combine, delay, modify, or otherwise adjust interactions with its collaborators, and all of that can break pretty much any test double you write. The art of writing a flexible test is to rely on as few implementation details as possible, balancing the risk of brittleness against the requirement to thoroughly test the system and its interactions with the outside world.

All of that said, to directly answer the question "how can using mocks be of any use", see JB Nizet's great analogy here: If you're trying to create a bomb detonator, you probably want to test it, but the cost of using the real thing is simply too great. The difficulty involved (and the optimal choice of test double) will vary based on whether the bomb in question has a hundred little triggers, or a single boom() method.

For more details about test doubles (a category that includes mocks, fakes, and dummy objects) and their pros and cons, see Martin Fowler's article "Mocks aren't Stubs".

Upvotes: 1

jdonmoyer
jdonmoyer

Reputation: 1270

I would suggest that you only mock the bare minimum of what's needed. Mockito has many facilities for doing this (spies, ability to return specific data/mocks when a method is invoked a certain way, etc.) but it ultimately comes down to having testable "seams" in your code. If you haven't already, I would recommend reading Michael Feather's book Working Effectively with Legacy Code for many suggestions on how to do this.

Upvotes: 0

Related Questions