Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Tomek Kaczanowski - Practical Unit Testing with JUnit and Mockito - 2013.pdf
Скачиваний:
228
Добавлен:
07.03.2016
Размер:
6.59 Mб
Скачать

Chapter 5. Mocks, Stubs, Test Spies

• tell them how they should behave (e.g. when(myFerrari.needsFuel()).thenReturn(true) or

when(myFerrari.needsFuel()).thenThrow(new RuntimeException())),

• verify that some of their methods have been called (e.g. verify(myFerrari).needsFuel()).

For the sake of a more complete picture, we should mention here that Mockito also has some limitations. In particular, it will not help you when working with code which does not adhere to certain design standards (in particular, Mockito works well with loosely coupled code). This will be further discussed in Section 7.6.

5.2. Types of Test Double

Now that we know the basics of Mockito, we can move on to the real topic of this chapter. It is time to learn more about test doubles and start using them for some testing.

The point of unit tests is to verify the correctness of our own code. What we want is to shield a particular piece of code from distractions introduced by web services, a file system, other modules, databases or third-party software, and then check if it works as expected. In order to achieve such a state, strict control over the SUT’s collaborators must be achieved. In this way it will be possible to test the SUT in isolation, which is an important characteristic of unit tests (see Section 2.1). Right now we are going to take a look at the various types of testing objects that can be used to replace the real collaborators of a class.

All such objects are grouped under just the one umbrella term – namely, test doubles. We will encounter four types of such objects. They are briefly described in Table 5.1. Do not worry if the descriptions sound a little bit cryptic. Everything will be clear once we have discussed each test-double type alongside some sample code.

Table 5.1. Types of test doubles

test-double type

also known as

description

 

 

 

dummy object

dummy

needs to exist, no real collaboration needed

 

 

 

test stub

stub

used for passing some values to the SUT ("indirect

 

 

inputs")

 

 

 

test spy

spy

used to verify if the SUT calls specific methods of the

 

 

collaborator ("indirect outputs")

mock object

mock

 

 

 

Figure 5.1 shows how test stubs, test spies and mocks cover a different aspect of communication between the test class, the SUT and its DOCs.

Figure 5.1. Test doubles covering inputs and outputs

71

Chapter 5. Mocks, Stubs, Test Spies

Based on this data, we may arrive at the following observations:

Dummies and stubs are used to prepare the environment for testing. They are not used for verification. A dummy is employed to be passed as a value (e.g. as a parameter of a direct method call), while a stub passes some data to the SUT, substituting for one of its DOCs.

The purpose of test spies and mocks is to verify the correctness of the communication between the

SUT and DOCs. Yet they differ in how they are used in test code, and that is why they have distinct names6. Both can also participate in test-fixture setting; however, this is only their secondary purpose.

No test double is used for direct outputs of the SUT, as it can be tested directly by observing its responses.

Let us mention, here, that even though in theory all test doubles have well defined roles and responsibilities, this is not the case in real-life programming. You will often encounter, and probably even yourself write, test code in which a test double plays more than one role. Typically, this happens with test spies and mocks, which are also frequently used in order to provide indirect inputs to the SUT, thus also playing the role of stub.

Fake

For the sake of completeness, let us describe another type of test double: a fake. Fake works almost as good as the real collaborator, but is somehow simpler and/or weaker (which makes it not suitable for production use). It is also usually "cheaper" in use (i.e. faster or simpler to set up), which makes it suited to tests (which should run as fast as possible). A typical example is an in-memory database that is used instead of a full-blown database server. It can be used for some tests, as it serves SQL requests pretty well; however, you would not want to use it in a production environment. In tests, fake plays a similar role to dummy and stub: it is a part of the environment (test fixture), not an object of verification. Fakes are used in integration tests rather than in unit tests, so we will not be discussing them any further.

5.2.1. Code To Be Tested with Test Doubles

Now that we know what types of test double there are, we can start using them. First, let us discuss a few lines of code, which will be used to demonstrate the role of test doubles in testing.

The code in Listing 5.12 is responsible for the sending of messages. It retrieves some data (i.e. the email of a client) from the parameters of sendMessage() method, then it tells its collaborators to perform certain actions. The code is trivial, yet quite close to reality (the real one would probably do some arguments checking and exception handling - we will skip both for the sake of simplicity).

The idea is to present the usefulness of each test double using some realistic example. Testing such a class with unit tests is almost never enough: you should also have some higher-level (i.e. integration) tests. However, that lies beyond the scope of this book. The issue is discussed further in Section 5.3.

6Please refer to Appendix C, Test Spy vs. Mock for a detailed comparison of test spy and mock.

72

Chapter 5. Mocks, Stubs, Test Spies

Listing 5.12. Class to be tested with test doubles

public class Messenger {

private TemplateEngine templateEngine; private MailServer mailServer;

public Messenger(MailServer mailServer, TemplateEngine templateEngine) {

this.mailServer = mailServer; this.templateEngine = templateEngine;

}

public void sendMessage(Client client, Template template) { String msgContent =

templateEngine.prepareMessage(template, client); mailServer.send(client.getEmail(), msgContent);

}

}

All collaborators injected using constructor.

This method returns void, and does not change the state of the Messenger object, which means there is no way we can test it using the state-testing approach.

This is what we are really interested in: the cooperation between the Messenger and its collaborators. Our SUT invokes some methods on each of its collaborators and also retrieves some data from the client parameter of the sendMessage() method.

This code illustrates pretty well our previous discussion about OO systems. The Messenger class takes care of transforming and passing messages rather than doing any real work. mailServer, a collaborator of our class, is expected to perform the real work, but it probably also passes the message to some thirdparty library. According to the previous discussion, our class is a manager, as it tells others what to do rather than getting its own hands dirty.

Figure 5.2. Interactions of Messenger with the test class and DOCs

As shown in Figure 5.2 collaborators interact differently with the SUT.

The testing of this sendMessage() method is problematic. It does not return anything, so the results of its work cannot be directly observed. It does not even change the state of the client or template object, so we cannot use these for verification. What we could do is use it to implement a real template processing engine and to set up a real mail server. We could also use a real mail client to verify the delivery of an e-mail. Such a thing is possible, but it takes us far away from unit tests, into the realm of integration tests. Such an integration test would require a great deal more preparation (a test fixture), and would run more slowly. In cases of failure, it would also be much harder to decide where the bug is – in the SUT, in collaborators' code, or maybe in the database storage implementation.

73

Chapter 5. Mocks, Stubs, Test Spies

Right now our goal is to test this class in isolation, so we need to think about something other than integration tests. If only we could verify the interactions of the SUT and DOCs… Then we could make sure that it works as expected. Well, thanks to test doubles this is possible. Using them, we can:

fully control the context of the working of the SUT,

eavesdrop on communication between the SUT and DOCs.

We will start with the simplest one - the dummy - and then proceed with the more interesting test doubles - the stub, test spy and mock. Eventually we will see how they all play together and form a valid test case. Our goal is not really to test the class in question, but rather to understand how each of the test doubles can help us in testing.

What should be unit-tested?

When discussing unit tests that verify behaviour it is quite important to grasp what it is that is supposed to be verified. As regards the Messenger class, we can verify only one thing: whether the send() method of mailServer is invoked with the same argument value as that provided by the client parameter and by the templateEngine collaborator. Why only this? Because that is the whole logic of the tested class! As discussed previously, our SUT is a "manager" – it coordinates the work of others, doing very little by itself. And with a unit test, the only thing we can test is this coordination part.

5.2.2. The Dummy Object

Take a look at the template parameter of the sendMessage() method presented in Listing 5.12. It is used only as an input parameter to another method - templateEngine.prepareMessage(). This is where a dummy can be used.

We are already familiar with the static mock() method of the org.mockito.Mockito class (see Section 5.1.1). It takes a Class as an argument and returns an object of this class - in this case of the Template type. Now we can use the template object in our tests: it is a perfect replacement for a real Template object.

Listing 5.13. Using a test double as a dummy object

Template template = mock(Template.class);

sut.sendMessage(client, template);

Creation of a dummy object. We assign a real object to it, generated using the mock() method. Execution of the SUT’s method using a dummy template object as one of the parameters.

To sum up, a dummy object is required just to execute the test, but not really needed for anything useful. It simply has "to be there". In some cases null will suffice, but in general it would be more far-sighted to use a mocking framework to generate an object of the requested type.

You probably will not use dummies a lot. In my experience they are rarely required.

Maybe you have already noticed that it is enough to pass a null value as a template parameter. That would work, but in general it is not recommended. It is probable that the method being tested will sooner or later be extended with some arguments checking functionality, so null values will not be accepted. Then your test will break, which is always frustrating. Instead

74

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]