Stubs are a mechanism for faking the behavior of real code that may exist or that
may not have been written yet. Stubs allow you to test a portion of a system without
the other part being available. They usually do not change the code you’re
testing but instead adapt to provide seamless integration.
DEFINITION stub—A stub is a portion of code that is inserted at runtime in place of
the real code, in order to isolate calling code from the real implementation.
The intent is to replace a complex behavior with a simpler one
that allows independent testing of some portion of the real code.
Here are some examples of when you might use stubs:
Ø When you cannot modify an existing system because it is too complex and
fragile
Ø For coarse-grained testing, such as integration testing between different
subsystems
Stubs usually provide very good confidence in the system being tested. With stubs,
you are not modifying the objects under test, and what you are testing is the same
as what will execute in production. Tests involving stubs are usually executed in
their running environment, providing additional confidence.
On the downside, stubs are usually hard to write, especially when the system to
fake is complex. The stub needs to implement the same logic as the code it is
replacing, and that is difficult to get right for complex logic. This issue often leads
to having to debug the stubs! Here are some cons of stubbing:
Ø Stubs are often complex to write and need debugging themselves.
Ø Stubs can be difficult to maintain because they’re complex.
Ø A stub does not lend itself well to fine-grained unit testing.
Ø Each situation requires a different strategy.
In general, stubs are better adapted for replacing coarse-grained portions of code.
You would usually use stubs to replace a full-blown external system like a filesystem,
a connection to a server, a database, and so forth. Using stubs to replace a
method call to a single class can be done, but it is more difficult. (We will demonstrate
how to do this with mock objects in chapter 7.)
Mock
Unit-testing each method in isolation from the other methods or the environment
is certainly a nice goal. But how do you perform this feat? You saw in chapter 6
how the stubbing technique lets you unit-test portions of code by isolating them
from the environment (for example, by stubbing a web server, the filesystem, a
database, and so on). But what about fine-grained isolation like being able to isolate
a method call to another class? Is that possible? Can you achieve this without
deploying huge amounts of energy that would negate the benefits of having tests?
The answer is, “Yes! It is possible.” The technique is called mock objects. Tim
Mackinnon, Steve Freeman, and Philip Craig first presented the mock objects
concept at XP2000. The mock-objects strategy allows you to unit-test at the finest
possible level and develop method by method, while providing you with unit tests
for each method.
Testing in isolation offers strong benefits, such as the ability to test code that has
not yet been written (as long as you at least have an interface to work with). In
addition, testing in isolation helps teams unit-test one part of the code without
waiting for all the other parts.
But perhaps the biggest advantage is the ability to write focused tests that test
only a single method, without side effects resulting from other objects being
called from the method under test. Small is beautiful. Writing small, focused tests
is a tremendous help; small tests are easy to understand and do not break when
other parts of the code are changed. Remember that one of the benefits of having
a suite of unit tests is the courage it gives you to refactor mercilessly—the unit tests
act as a safeguard against regression. If you have large tests and your refactoring
introduces a bug, several tests will fail; that result will tell you that there is a bug
somewhere, but you won’t know where. With fine-grained tests, potentially fewer
tests will be affected, and they will provide precise messages that pinpoint the
exact cause of the breakage.
Mock objects (or mocks for short) are perfectly suited for testing a portion of
code logic in isolation from the rest of the code. Mocks replace the objects with
which your methods under test collaborate, thus offering a layer of isolation. In
that sense, they are similar to stubs. However, this is where the similarity ends,
because mocks do not implement any logic: They are empty shells that provide
methods to let the tests control the behavior of all the business methods of the
faked class.
DEFINITION mock object—A mock object (or mock for short) is an object created to
stand in for an object that your code will be collaborating with. Your
code can call methods on the mock object, which will deliver results
as set up by your tests.
In your tests, you have sometimes used the real objects and sometimes
mocked them.
Here are some cases in which mocks provide useful advantages over the real
objects. (This list can be found on the C2 Wiki at http://c2.com/cgi/wiki?Mock-
Object.) This should help you decide when to use a mock:
Ø Real object has non-deterministic behavior
Ø Real object is difficult to set up
Ø Real object has behavior that is hard to cause (such as a network error)
Ø Real object is slow
Ø Real object has (or is) a UI
Ø Test needs to query the object, but the queries are not available in the real
object (for example, “was this callback called?”)
Ø Real object does not yet exist
JUnit best practices: don’t write business logic in mock objects
The single most important point to consider when writing a mock is that it
should not have any business logic. It must be a dumb object that only does
what the test tells it to do. In other words, it is purely driven by the tests. This
characteristic is exactly the opposite of stubs, which contain all the logic (see
chapter 6).
There are two nice corollaries. First, mock objects can be easily generated, as
you will see in following chapters. Second, because mock objects are empty
shells, they are too simple to break and do not need testing themselves.
JUnit best practices: only test what can possibly break
You may have noticed that you did not mock the Account class. The reason is
that this data-access object class does not need to be mocked—it does not depend
on the environment, and it’s very simple. Your other tests use the Account
object, so they test it indirectly. If it failed to operate correctly, the tests
that rely on Account would fail and alert you to the problem.
Design patterns in action: Inversion of Control (IOC)
Applying the IOC pattern to a class means removing the creation of all object
instances for which this class is not directly responsible and passing any needed
instances instead. The instances may be passed using a specific constructor,
using a setter, or as parameters of the methods needing them. It becomes
the responsibility of the calling code to correctly set these domain objects on
the called class.
Using mocks as Trojan horses
DEFINITION expectation—When we’re talking about mock objects, an expectation is a
feature built into the mock that verifies whether the external class calling
this mock has the correct behavior. For example, a database connection
mock could verify that the close method on the connection is
called exactly once during any test that involves code using this mock.