8.3. Creating partial mocks with spies

In this section you’ll see how to create partial mocks.[8] Chapter 6 explained how Spock can create fake objects that are useful for testing and showed you mocks and stubs. Spock supports a third type of “fake” object: spies.

8

And why you shouldn’t use them!

Spies, shown in figure 8.5, work as partial mocks. They take over a Java object and mock only some of its methods. Method calls can either by stubbed (like mocks) or can pass through to the real object.

Figure 8.5. A spy is a real class in which only a subset of methods are fake. The rest are the real methods.

I purposely didn’t show you spies in chapter 6 because they’re a controversial technique that implies problematic Java code. They can be useful in a narrow set of cases. Their primary use is in creating unit tests for badly designed production code that can’t be refactored (a common scenario with legacy code).

8.3.1. A sample application with special requirements

Let’s see an example that’s well-suited for writing a Spock test with spies instead of mocks/stubs. Say you’re tasked with the development of unit tests for an existing Java application. The Java application in question is a security utility that gets video feed from an external camera, and upon detecting intruders, deletes all files of the hard drive (to hide incriminating evidence).

The application code is implemented by two Java classes. The first class is responsible for deleting the hard drive, and the second class implements the face-recognition algorithms that decide whether the person in front of the camera is a friend or enemy, as shown in the next listing.

Listing 8.23. Java code with questionable design

You should instantly see the flawed design of this Java code. Figure 8.6 provides an overview.

Figure 8.6. Hard drive deletion logic is hidden inside the face-recognition logic

The application doesn’t use dependency injection. Instead of splitting responsibilities into separate entities, the application contains both the logic of deletion and the face recognition in a single “object.”

You see this design flaw and start refactoring the application in order to write your unit tests. Unfortunately, your boss says that the binary application is digitally signed, and changing even the slightest thing in the source code will create an invalid signature.[9] Your boss adds that even if you successfully refactor the code, your department doesn’t have access to the digital certificate, so you couldn’t re-sign the binary after your change.

9

My example is a bit extreme. Usually code can’t be changed for political reasons.

You need to write a unit test with the source code as is. You’re asked to examine the effectiveness of the face-recognition software by using images of both kinds (those that have a threat and those that don’t). This is one of the rare occasions that spies can be employed for unit testing.

8.3.2. Spies with Spock

You need to write a unit test that examines the activate() method of the SmartHardDriveNuker class. You know that behind the scenes it calls the deleteHardDriveNow() method. It wouldn’t be realistic to delete your hard drive each time you write a unit test that triggers the face-recognition logic. You need to find a way to mock the dangerous method while the real method of the face-recognition logic is kept as is.

Spock supports the creation of spies, as shown in the next listing. A spy is a fake object that automatically calls the real methods of a class unless they’re explicitly mocked.

Listing 8.24. Creating a spy with Spock

Here you create a spy of your class under test. By default, after creation, all methods are real and pass through to the real object.[10] Then you specifically mock the method that deletes the hard drive. But the method that employs the face-recognition logic is still the real one.

10

Creating a spy without mocking any method is the same as using the object itself—not very exciting.

When the activate() method is called, it runs its real code (so you can pass it different images and test the effectiveness of the face-recognition code). In the case of an image that represents a “threat” and so triggers the hard drive deletion process, you know that the mocked method will be called (and thus your hard drive is safe).

This listing shows only one test, but in reality you’d need to write a parameterized test with multiple images that examines the behavior of the face-recognition code.

8.3.3. The need for spies shows a problematic code base

Spies are used for legacy code primarily because of the bad quality of legacy code.[11] Well-designed code doesn’t ever need spies in the first place. Figure 8.7 shows a flow diagram of using spies that you should keep in your head at all times. The diagram isn’t specific to Spock. It applies to all testing frameworks (including Mockito).

11

A universal fact: legacy code is always badly designed code.

Figure 8.7. Spies can always be replaced with mocks in well-designed code.

In the example of the security utility, a spy is essential because the Java code doesn’t use dependency injection. This is just one of the code smells of badly designed code. Java code that comes as a big ball of mud,[12] breaks the SOLID principles,[13] contains God[14] objects, and generally suffers from big design flaws isn’t directly testable with mocks/stubs, and spies are needed.

12

13

14

In those cases, you should resist the temptation to write a Spock test with spies and instead refactor the code before writing your unit tests. You’ll find that in most cases (if not all), spies aren’t needed after the refactoring is complete.

8.3.4. Replacement of spies with mock

You use spies with the security utility because you can’t refactor the Java code first, as this would invalidate the digital signature of the binary. If that constraint didn’t hold, you’d instead modify the Java code to properly use dependency injection. An obvious decoupling of dependencies is shown in the following listing.

Listing 8.25. Refactoring Java code to avoid spies

Here you refactor your Java code to use composition instead of inheritance. You also introduce the “dangerous” hard drive deletion code as an external dependency. After this refactoring, you can rewrite your unit test by using a normal mock, as shown in the next listing.

Listing 8.26. Using a mock instead of a spy

If you’ve read chapter 6, the code in listing 8.26 should be easy to understand. Because you’ve refactored the Java code and hard drive deletion is now an external dependency, you can mock that class and pass it the face-recognition code. This way, your class under test—SmartHardDriveNuker—is a real one, and a mock is used for the collaborator class: HardDriveNuker.

The end result is that no spies are used. What you need to take away from this section of the book is that despite Spock support for spies, you should avoid using them, and instead spend time improving the design of your code so that spies aren’t needed.

And with that knowledge about spies, we conclude this book! You can now put it down and go write your own Spock tests!

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

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