16
Integration testing 1. Introduction In this lab we will learn how to write integration tests. Sometimes the software to be tested has an external component that the developer/tester has no control of (e.g. it’s not fully developed yet) and it’s the developer’s (or QA’s) job to fully test the service without the existence of the other service with which the tested service will interact with. In this lab we will learn how to mock non- existing Java classes and ensure that the requests this service receives return the expected result. Useful vocabulary: Integration testing is a software testing method where you test individual software components or units of code to confirm proper interaction between various software components. Mock objects are objects that mimic the behaviour of a real objected in a controlled manner. Callback is any executable code that is passed as an argument to other code that is expected to call back (execute) the argument at a given time. In plain English, a callback function is like a Worker who "calls back" to his Manager when he has completed a Task. Mobile payment is a transaction that takes place through a mobile device. You can take Google as an example. If you have Telia as your operator in Estonia, then you can purchase content on Google Play without using a credit card. It can be done by choosing Telia as your Payment method when purchasing content. Operator is a mobile network operator. (e.g. Telia and Tele2)

Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Integration testing

1. Introduction

In this lab we will learn how to write integration tests. Sometimes the software to be tested has an

external component that the developer/tester has no control of (e.g. it’s not fully developed yet)

and it’s the developer’s (or QA’s) job to fully test the service without the existence of the other

service with which the tested service will interact with. In this lab we will learn how to mock non-

existing Java classes and ensure that the requests this service receives return the expected

result.

Useful vocabulary:

Integration testing is a software testing method where you test individual software

components or units of code to confirm proper interaction between various software

components.

Mock objects are objects that mimic the behaviour of a real objected in a controlled

manner.

Callback is any executable code that is passed as an argument to other code that is

expected to call back (execute) the argument at a given time. In plain English, a

callback function is like a Worker who "calls back" to his Manager when he has

completed a Task.

Mobile payment is a transaction that takes place through a mobile device. You can

take Google as an example. If you have Telia as your operator in Estonia, then you

can purchase content on Google Play without using a credit card. It can be done by

choosing Telia as your Payment method when purchasing content.

Operator is a mobile network operator. (e.g. Telia and Tele2)

Page 2: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

2. Service to be tested: Operator Communicator

The service that is under testing is a mobile payment service. A user has given their consent to

a merchant (e.g. a gaming company) to charge the user, so they can receive extra content from

their product.

Summary of components involved in the flow:

User – a mobile phone user who wishes to purchase a product, but does not have a credit card.

In this case, he or she will purchase the product through their mobile phone operator.

Merchant – a person or a company that provides a digital service where users can purchase

digital goods.

Merchant Communicator – a microservice that handles communication between the merchant’s

backend and other microservices.

Operator Communicator – a microservice that handles communication between the Merchant

Communicator and the operator’s platform.

Operator – mobile network operator of the user.

Figure 1 shows business-side flows that are to be implemented in the service.

Figure 1. Business-side logic for Operator Communicator

Page 3: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Flow explanation:

Let’s say the user is playing a mobile game and wants extra paid content to enjoy it even more.

For that the user gives the developer of the game – the merchant – a request to bill his mobile

number to get the paid content. The merchant then continues to communicate with its partners

that handle all the mobile billing for them (which is basically us, the developer of the software to

be tested). First, the notification is sent to Merchant Communicator, which handles the

communication between the merchant and the Operator Communicator. The Operator

Communicator handles all communication done to the operator’s API. During the “success flow”,

the Operator tries to charge the user on their side and - since it is successful - it responds with

code “0”. Since the response given by the Operator is 0, we need to translate this response into

a string OK that our system can understand (see box “translates as ok”)”. Next, the Operator

Communicator tells the Merchant Communicator that the charge was successful, so it can notify

the merchant that the user has been charged and should receive the extra content they paid for.

However, if the Operator did not manage to charge the user (see Figure 1 red flow), it notifies

failure with the response code “1”. Operator Communicator translates this as a failure and the

merchant is notified that the user has not been charged and should not receive the extra content

they wished to buy.

The system under test (SUT) is Operator Communicator. Currently, it’s the only service along

with the Operator’s API that exists.

MerchantCommunicator and the Operator are components that we will mock. In case of the

Operator, it’s because it’s a third party component whereas the MerchantCommunicator is not

implemented yet.

In this lab we will first write tests for PaymentService, where we will mock the OperatorClient

and when creating tests for OperatorClient, we mock the Operator’s server.

Page 4: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

3. Writing integration tests

Project setup

You are provided with a project that includes the implementation of the Operator Communicator

component. To understand the structure of the classes in the project, see Appendix 3.

1. Download the operator-communicator-student-version file from the lab page.

2. Make sure that you have JDK 8 and gradle plugin for IntelliJ (Tutorial:

https://www.jetbrains.org/intellij/sdk/docs/tutorials/build_system/prerequisites.html).

3. In IntelliJ: New -> Project From Existing Sources…

4. Pick the operator-communicator-student-version

5. Let gradle build your project

6. Test if everything works by running the tests (Right click on test folder and click Run ‘All

tests’

Figure 2. Technical flow of Operator Communicator

The flow (see figure 2) begins in PaymentService class with method

initiatePaymentToOperator().

What approach to take when mocking?

Starting with mocking is simple, but knowing what to mock can be quite difficult. Firstly, you have

to understand what mocking is and what possibilities it offers.

Mocking is a process used in unit testing where the unit under testing is dependant on external

components. It is used when we want to focus on testing the unit under testing and not the state

and behavior of the other components it depends on. In mocking, those dependencies are

replaced by controlled replacement objects that simulate the behavior of actual components.

Now that you’re somewhat sure what mocking is, I’m going to provide you with hints on what to

look out for when mocking.

A big hint: look for HTTP requests. Classes that do HTTP requests need to be mocked, because

we don’t want to do real requests against the API when we run tests - it would be problematic,

especially if it’s against an actual URL.

Page 5: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

For example: OperatorClient’s only job is to do HTTP requests against the operator’s API,

therefore mocking the server should be the best choice for these types of tests – it’s only parsing

operator’s responses into the appropriate models. But PaymentService is only interested in the

values those models receive, therefore when().thenReturn() approach would be sufficient.

Look at the classes under testing and what methods they go through. Decide if we need to mock

anything internally that isn’t a simple constant.

You can test the two classes in one way, but however as we want to learn as much ways of

mocking in unit tests as possible, we will be covering two different ways of testing.

In this lab we will first write tests for PaymentService, where we will mock the OperatorClient and

when creating tests for OperatorClient, we mock the Operator’s server.

Page 6: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Testing with MockWebServer

You can find detailed information about MockWebServer here:

https://github.com/square/okhttp/tree/master/mockwebserver

MockWebServer mocks the behaviour of another web server. This will be used to mock the

Operator’s responses to our code and this will also help us assert that the correct values are sent

in the callback to the merchant.

Useful code snippets:

To initiate a MockWebServer:

MockWebServer mockWebServer = new MockWebServer();

In case you want to change the port of the server (ex. On port 9000):

mockWebServer.start(9000);

To mock the response of the MockWebServer:

mockWebServer.enqueue(new MockResponse().setBody(“Hello world!”));

NB! The response code of the HTTP request will be 200 by default. However if you

want to change that then you can set it by adding.setResponseCode(400) to the

MockResponse.

To close the mocked web server:

mockWebServer.shutdown();

Here is an example on how to write tests for Java classes which interact with external servers

via HTTP, with MockWebServer:

We first set up the @Before and @After. In @Before we also initiate the MockWebServer.

private MockWebServer operatorServer;

@Before

public void setUp() throws IOException {

this.operatorServer = new MockWebServer();

this.operatorServer.start(9000);

}

Page 7: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

@After

public void tearDown() throws IOException {

this.operatorServer.shutdown();

}

We will also initiate a new OperatorClient object - the Java class that will be doing the actual

request.

final private OperatorClient client = new OperatorClient();

Then we queue the response the MockWebServer will return in JSON format using the enqueue

method. The response body will be set as {“code”: “0”}, because we know the Operator sends

parameter “code” containing the value “0” when the request succeeds (see Table 4) and that the

response body will be in JSON format.

Now we initiate the requestPaymentForUser() method in the client.

final OperatorResponse response = this.client.requestPaymentForUser(new

OperatorRequest());

Then it will be a successfully made into a model

Now we assert if the response is parsed into a model properly.

assertEquals("0", response.getCode());

Page 8: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Testing with Mockito and ArgumentCaptor

Read more here: https://javacodehouse.com/blog/mockito-tutorial/

With Mockito you can mock the object the mocked class will return to you, but remember that it’s

not good practice to mock everything, because if the changes are made in code, the tests will still

pass. It is good practice to mock anything that you have no control of.

Using when-then is almost self-explanatory. It tells the Mockito framework that when you reach

this line of code, then return this value instead and proceed.

Useful code snippets:

You can mock what a method of an object returns:

when(callback.getUrl()).thenReturn(“http://localhost:9000/callback”);

You can also mock that the method returns an exception:

when(callback.getUrl()).thenThrow(exception);

Here is an example on how you can test in various ways using Mockito and how to verify values

using ArgumentCaptor.

ArgumentCaptor is used to capture argumeny values for further assertions. For this case, we

want to assert that the object values passed to OperatorClient were as expected.

Example:

ArgumentCaptor<Person> varArgs = ArgumentCaptor.forClass(Person.class);

verify(mock).varArgMethod(varArgs.capture());

List expected = asList(new Person("John"), new Person("Jane"));

assertEquals(expected, varArgs.getAllValues());

Read more: https://static.javadoc.io/org.mockito/mockito-

core/2.6.9/org/mockito/ArgumentCaptor.html

Before we start to write the actual test cases, we have to prepare the test class to use Mockito

mocks.

First, we initiate the class rule. This is necessary so that the Mocks we want to use will be

initialised before each test method.

@Rule

public MockitoRule mockitoRule = MockitoJUnit.rule();

Page 9: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Next, we will inform the test class that we want to mock OperatorClient. The @Mock annotation

informs Mockito this will be a mocked object.

@Mock

private OperatorClient operatorClient;

Then we will create a new PaymentService object and use the @InjectMocks annotation, which

will create an instance of the class and every class that has a @Mock annotation (see

OperatorClient above) will be injected into this instance.

@InjectMocks

private PaymentService paymentService = new PaymentService();

From the Operator Communicator API provided above (see 2.1) we can see the parameters that

will initiate the PaymentService class initiatePaymentToOperator() method (see Table 1) and

what the possible returned end results are (see Table 2).

Based on this information, we will hard code the driver of the method that we will test. To make

the testing clear, we will create the object in a private method in the class.

private PaymentRequest generatePaymentRequest() {

final PaymentRequest request = new PaymentRequest();

request.setAmount("10");

request.setMerchantId("1");

request.setPaymentId("test_payment_id");

request.setUserPhoneNumber("552211994");

return request;

}

Now the preparations are done and we can begin writing actual tests.

The first thing we need to do in the test is to mock the response OperatorClient will return.

Instead of using MockWebServer to mock the response the operator will send, we mock the

result the OperatorClient should return whenever the request is successful.

For that we use Mockito’s methods when(<this mocked object initiates this method with

these values>).thenReturn(<this object instead of going into the method>).

Page 10: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Based on the information provided above (see 2.2 Table 4), we know that in a success case,

the value of “code” in the operator’s response should be “0”. We will hard code the object and

place it as parameter for the thenReturn() method.

private OperatorResponse generateSuccessfulResponse() {

OperatorResponse response = new OperatorResponse();

response.setCode("0");

return response;

}

when(operatorClient.requestPaymentForUser(any(OperatorRequest.class))

.thenReturn(this.generateSucceedResponse());

As you can see we put the Mockito method any(OperatorRequest.class) as a parameter for

the requestPaymentForUser() method. This means that it will provide thenReturn() when any

objects of OperatorRequest class are passed to the requestPaymentForUser() method.

Now we also know that we need to pass a PaymentRequest object into the

initiatePaymentToOperator() method to initiate it. We could add an empty object, but as we

want to assert if the values of the object OperatorRequest that are passed to the mocked

OperatorClient, we will use the generatePaymentRequest() method we created above during

test preparations.

We initiate the payment service and save the result as a separate object for assertions:

final PaymentResult result =

this.paymentService.initiatePaymentToOperator(this.generatePaymentRequest());

First, we assert that the response was OK as it’s the most critical for our service.

assertEquals(Constant.OK, result.getResult());

Then we want to assert if the OperatorRequest parameters were passed to the mocked object

as expected and if the values are correct. This is important as the Operator expects the values

to be in accordance to their API. If some values don’t reach the operator then the success flow

will fail in production.

Now we will learn how to use ArgumentCaptor.

Page 11: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

First we need to create an ArgumentCaptor for OperatorRequest object, because we want to

assert what parameters were passed to the OperatorClient.

ArgumentCaptor<OperatorRequest> operatorRequestArgumentCaptor =

ArgumentCaptor.forClass(OperatorRequest.class);

Next we use Mockito’s verify() method to verify if the method for this mocked object was called

and assign the ArgumentCaptor we created along with the method call capture() as a

parameter for requestPaymentForUser(). This means that the ArgumentCaptor will fetch any

objects that are passed where it’s assigned.

verify(operatorClient).requestPaymentForUser(operatorRequestArgumentCaptor.capture());

Now we can check the values of the object the ArgumentCaptor caught. To get the object we

use the getValue() method.

assertEquals("10",operatorRequestArgumentCaptor.getValue().getAmount());

assertEquals("test_payment_id",

operatorRequestArgumentCaptor.getValue().getPartnerId());

assertEquals("552211994",

operatorRequestArgumentCaptor.getValue().getUserNumber());

To write tests, you have to understand what values are needed to create a request and a

response. Usually when integrating with other APIs, you are provided with an API document

(see Appendix 1 and 2). You need to take them into account (especially the types of the

parameters) when creating a request to the operator.

Page 12: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

4. Additional exercises for students

1. Write a failure case for OperatorClient using MockWebServer

2. Write a failure case for PaymentService using Mockito

Page 13: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Appendix 1. Operator Communicator API request and response parameters

For this SUT, the Merchant Communicator sends the following parameters, which are to be used

for generating a payment request to the Operator:

Table 1. Request parameters received by Operator Communicator (PaymentService class) from

Merchant Communicator

Parameter name Parameter type Explanation Example value

paymentId String Unique id for the

payment process

1

merchantId String Merchant specific

unique id

0001

userPhoneNumber String User phone number

that will be charged

552211994

amount String The amount the user

needs to be charged

10

In return the following response from the Operator Communicator is expected by Merchant

Communicator:

Table 2. Response parameters from Operator Communicator (PaymentService class) to

Merchant Communicator

Parameter name Parameter type Explanation Example values

result String The result of the

payment request.

If the charge was

successful then it

should be given the

value “OK”, otherwise

it should be given the

value “failed”. No

other results are

expected.

Page 14: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical
Page 15: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Appendix 2. Operator API request and response parameters

Here are the required parameters for the request:

Table 3. Request parameters from the Operator Communicator received by the Operator

Parameter name Parameter type Explanation Example values

amount String The amount the user

needs to be charged

10

partnerPaymentId String Partner specific

unique id. The partner

is us, who will be the

communication line

between the

merchant and the

operator.

0cf833c6-4ed1-

11e9-8647-

d663bd873d93

userPhoneNumber String The number that will

be charged.

556677889

Here is the response the operator will send:

Table 4. Response parameters from the Operator sent to the Operator Communicator

Parameter name Parameter type Explanation Example values

code String The result of the

charge

If the user was

successfully charged,

then the operator will

return “0”, and if it

failed it will return “1”

NB! During the writing of the test, you may use custom values or values provided in the example

values below, but operator responses should never be custom as it’s part of a component that

isn’t developed by us.

Page 16: Integration testing - ut...5. Let gradle build your project 6. Test if everything works by running the tests (Right click on test folder and click Run ‘All tests’ Figure 2. Technical

Appendix 3. Structure of classes

Http

OperatorClient

A class that directly communicates with the operator’s API via HTTP

request.

It parses the JSON request body into JSON format and forwards the

information to the operator’s endpoint.

Model

Operator

OperatorRequest

Model for the request parameters that the operator will require.

OperatorResponse

Model for the response parameters that the operator potentially

sends.

Payment

PaymentRequest

Model for the request parameters that reach the Operator

Communicator.

PaymentResponse

Model for the response parameters that reach the Operator

Communicator.

Service

PaymentService

The most important part of the flow, where requests and responses for

the mobile payment are handled.

Request is built and sent to the OperatorClient class, which

communicates with the operator.

Returns OK, when the operator responds with “0” as in the request was

successful and it translates everything else as FAILED.

Util

Constant

Contains constants that we frequently use