6.1. Using fake collaborators

Let’s say you have a Java class in your application that shuts down a nuclear reactor when radiation levels from a sensor pass a critical threshold. You want to write a unit test for that class. It wouldn’t be realistic to bombard the real sensor with radiation just to see your unit test work. It’s also not realistic to shut down the nuclear reactor whenever the unit test runs.

For a more run-of-the-mill example, assume that your Java class sends a reminder email to a customer whenever an invoice isn’t settled on time. Re-creating a delayed payment in the unit test would be difficult, and sending a real email each time the unit test runs would also be unrealistic.

For cases like this, you need to employ fake collaborators in your unit test. You need to fake the way your class gets the current sensor values as well as the communication with the nuclear reactor, and you also need to fake the delayed payment and the email server. A fake collaborator is a special class that replaces a real class in order to make its behavior deterministic (preprogrammed). No technical limitation ties fake classes to unit tests. But unit tests are much more flexible if they employ the power of fake classes (which is the running theme of this chapter).

A visual way to describe a fake object is shown in figure 6.1. The core of each circle is a class, and the arcs around it are its methods.[2]

2

Figure 6.1. By using fake classes, you can define your own sensor values and replace the real nuclear reactor with a fake one. You can safely mimic any possible situation you want without affecting the real hardware.

6.1.1. Using fake collaborators to isolate a class in unit tests

If a bug shows up in a real system (or in an integration test), it’s not immediately clear which class or classes are responsible for it. In a unit test that contains only a real class (as in figure 6.1), you can preprogram its collaborators with “correct” and “expected” answers. Thus, it’s easy to focus the unit test on how this single class works and make sure that it contains no bugs (at least on its own).

These fake collaborators aren’t real classes because

  • They implement only the methods needed for the unit test and nothing else.
  • When they make requests to the real class, the request parameters are preprogrammed and known in advance.
  • When they answer requests from the real class, their answers are also preprogrammed.

The point to remember is that only the programmer knows the existence of the fake classes. From the point of view of the real class, everything is running normally. The real class thinks that it runs on a live system (because the fake classes still implement the agreed-upon interfaces) without understanding that the whole stage is a single unit test.

For the rest of this chapter, I use the terms stub and mock for this type of fake class because this is the terminology used by Spock.

6.1.2. Faking classes in Spock: mocks and stubs

Mocks and stubs are a further subdivision of fake objects. Here are the definitions I first introduced in chapter 3:

  • A stub is a fake class that comes with preprogrammed return values. It’s injected into the class under test so that you have absolute control over what’s being tested as input. In figure 6.1, the fake sensor is a stub so that you can re-create any radiation levels you want.
  • A mock is a fake class that can be examined after the test is finished for its interactions with the class under test (for example, you can ask it whether a method was called or how many times it was called). In figure 6.1, the fake reactor class is a mock so that you can ask whether its shutdown() method was called after the unit test has ended.

In practice, because mocks can also be stubbed, you can think of them as a superset of stubs (which cannot be used for verification of interactions). In theory, you could write all your Spock tests using only mocks. For readability, it’s best to decide in advance which type of fake object you’re creating.

Unit testing and filmmaking analogy

If you still have trouble understanding the difference between mocks and stubs, imagine that instead of a programmer, you’re a film director. You want to shoot a scene (create a unit test). First you set up the actors, camera, and sound that will create the illusion of the scene (you prepare stubs and mocks). You let the camera roll (run the unit test) and check the camera screen for the result (examine the test result).

Stubs are your sound technician, your lighting experts, and your camera man. You give them instructions before each scene, as you preprogram your stubs. They’re essential for filming your scene, as stubs are essential for the correct functionality of your class under test. But they don’t appear in the recorded scene, as stubs are never used for test verification.

Mocks are your actors. You also give them a script before the scene for their dialogue, in the same way that you prepare your mocks (for their interaction with the class under test). After filming is finished, you check their onscreen performance as you check the interactions of your mocks.

Under dire circumstances, you can force an actor to hold the boom microphone or the camera (use a mock in place of a stub), but you can never create an actor out of a technician on the spot (you can’t use a stub for interaction verification).

6.1.3. Knowing when to use mocks and stubs

Knowing how to mock classes isn’t enough for effective unit tests. You also need to know which classes to mock out of all the collaborators. In a large enterprise project, your class under test might interface with several other classes. You should ask yourself which classes need mocking and which classes can use their real implementation.[3]

3

And which should be spies in some rare cases. Spies are explained in chapter 8.

I’ve seen both sides of the spectrum. Some developers don’t use mocking at all, making all unit tests run as integration tests. I’ve also seen excessive mocking of classes that don’t need it. The former situation isn’t desirable because all tests will be slow and complex. The latter situation has its own problems as well. Upgrading badly designed mocked tests is difficult when the production code changes because of unforeseen business requirements.

As a general rule of thumb, you should mock/stub all collaborator classes that do the following:

  • Make the unit test nondeterministic.
  • Have severe side effects.
  • Make the test depend on the computation environment.
  • Make the test slow.
  • Need to exhibit strange behavior typically not found on a real system.

The first case is obvious. If you’re writing a unit test for a game that uses electronic dice, you can’t possibly use the real dice class in your unit test. Instead, you mock it to make it return a particular number that fits your scenario.

The second case was demonstrated in chapter 3. If you have code that charges credit cards, prints documents, launches missiles, or shuts down nuclear reactors, you must mock it so that nothing bad happens when a unit test runs.

The third case is about creating reliable tests that always have the same behavior when they run either on the build server or a development workstation. Code that reads environment variables, reads external setting files, or asks for user input should be mocked with the variables used in that specific scenario.

In large enterprise projects, tests can be slow. A common speed-up technique is to mock out code that reads files, communicates with a database, or contacts an external network service. This makes the test CPU-bound instead of I/O-bound so tests run at the maximum capacity of the processor.

Finally, you need to stub objects when you want to create a specific behavior that’s hard to reproduce with the real class. Common cases are emulating a full hard disk or a complete failure in the network.

The corollary of the preceding list is that if you have a collaborator class that doesn’t depend on external services or the environment, doesn’t perform I/O operations, and is fast in its responses, then you can use it as is in a unit test (with its real implementation).

The class under test is always a real class

I’m always baffled by questions in Stack Overflow and other forums and mailing lists indicating that people have difficulties with mocking because they try to mock the class under test instead of its collaborators. Some advanced unit tests need this (I’ll show you spies in chapter 8), but this technique is only for extreme corner cases of legacy code. In vanilla unit tests, the class under test is always a real class. Mocks are used for its collaborators only. Even then, not all collaborators need to be mocked.

6.1.4. Exploring a sample application for an electronic shop system

The running example for this chapter is an expanded version of the e-shop system that you saw in chapter 4. Figure 6.2 shows a high-level overview of the system under test.

Figure 6.2. An extended e-shop that has an inventory and charges credit cards

In this e-shop, the client can browse product pages and add them to an electronic basket as before. But when the client checks out, two additional actions are performed. First, the inventory is checked to verify that the products are indeed ready for dispatching. Second, the credit card of the client is charged using an external preexisting service (which is the responsibility of another company).

The Java skeleton for this e-shop is shown in the following listing, and it’s similar to the one introduced in chapter 4.

Listing 6.1. Java skeleton code for the e-shop

Just by looking at figure 6.2, you can already guess which class you’ll stub and which class you’ll mock. The inventory class will be stubbed (so you can define the stock levels of various products, regardless of the real warehouse), and the credit card system will be mocked (so you don’t charge real credit cards when a unit test runs).

I’ll use this sample application throughout the chapter for increasingly complex examples of Spock unit tests that contain mocks and stubs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.163.238