1
INTEGRATION TESTING FOR
MICROSERVICES WITH SPRING BOOT
Oleksandr Romanov
2 – 3 March 2018
Kyiv, Ukraine
2
WHAT I WILL COVER?
1) Microservices architecture and it’s testing complexities
2) Anatomy of microservice
3) Effective integration testing with Spring Boot
4) Component tests with spring-boot-test-containers
5) Conclusions
3
WHO AM I
6+ years as Test Automation Engineer
Worked with Java / C# web applications
/ backend systems from CMS to enterprise
Working now as QA Automation Lead
at Playtika
4
GOOD OLD TESTING PYRAMID
UI
SERVICE
UNIT
5
MICROSERVICES ARCHITECTURE
Microservice
Microservice
Microservice
UI
WEB
MOBILE
6
ANATOMY OF MICROSERVICE
Microservice
External
services
Persistence Domain logic
7
MICROSERVICES TEST AUTOMATION APPROACH
End – to – End (UI / API) tests
Contract
tests
Component
tests
Integration
tests
Unit tests
Component
tests
Integration
tests
Unit tests
8
WHAT IS SPRING BOOT?
https://projects.spring.io/spring-boot/
9
MACHINE MICROSERVICE
- API: GET /machine?userId={id}
{
"service": {
"code": 0
},
"data": {
"machineId": 1,
"machineAvailable": true
}
}
10
UNIT TESTS
• Verify state or behavior of unit of code within
one single microservice
• Use stubbing / mocking
Component
tests
Integration
tests
Unit tests
11
INTEGRATION TESTS
Verify service integration logic with various
external sources
- persistence: MariaDB, Couchbase,
Aerospike, Neo4j, etc)
- gateway: HTTP REST API,
Message Queues, RPC, etc.
Component
tests
Integration
tests
Unit tests
12
INTEGRATION TESTS
Spring Boot “arsenal”:
o @DataJpaTest
o @DataMongoTest
o @JdbcTest
o @DataRedisTest
o @DataNeo4jTest
Microservice
Persistence
13
ADDING CONTAINER DEPENDENCIES
https://github.com/Playtika/testcontainers-spring-boot
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>embedded-mariadb</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>embedded-couchbase</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
14
CHECK OUT APPLICATION PROPERTIES
spring.datasource.url=jdbc:mariadb://${embedded.mariadb.host}
:${embedded.mariadb.port}/${embedded.mariadb.schema}
spring.datasource.username=${embedded.mariadb.user}
spring.datasource.password=${embedded.mariadb.password}
15
EXAMPLE REPOSITORY
interface MachineSettingRepository extends
Repository<MachineSettingEntity, Long> {
Stream<MachineSettingEntity> findAll();
}
16
WRITING SIMPLE SQL DB TEST
@Autowired
private TestEntityManager testEntityManager;
@Autowired
private MachineSettingRepository machineSettingRepository;
17
WRITING SIMPLE TEST FOR SQL DB
@Test
public void shouldFindSingleMachineSetting() throws Exception {
MachineSettingEntity settingEntity = new MachineSettingEntity(1,
“Test”);
testEntityManager.persistAndFlush(settingEntity);
assertThat(machineSettingRepository.findAll().filter(e ->
e.getMachineId() == settingEntity.getMachineId())
.collect(Collectors.toList()).get(0)).isEqualTo(settingEntity)
.withFailMessage("Unable to find machine setting with id " +
settingEntity.getMachineId());
}
18
RUNNING SQL DB TEST
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace =
AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(properties =
{"embedded.couchbase.enabled=false"}
public class MachineSettingDatabaseIntegrationTest
19
WRITING SIMPLE TEST FOR NOSQL DB
@Autowired
private MachineStateDocumentRepository machineStateDocumentRepository;
@Test
public void shouldFindStateInDb() {
MachineStateDocument testState = MachineStateDocument.builder()
.key("4-4")
.build();
machineStateDocumentRepository.save(testState);
assertThat(machineStateDocumentRepository.findOne("4-4").get())
.isEqualTo(testState)
.withFailMessage("Unable to find state by key: " + testState.getKey());
}
20
RUNNING NOSQL DB TEST
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest(classes = {MachineStateTestConfiguration.class})
public class MachineStateDatabaseIntegrationTest
21
ADDITIONAL CONFIGURATIONS
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
LiquibaseAutoConfiguration.class,
XADataSourceAutoConfiguration.class})
@TestConfiguration
@Profile("test")
@Import({CouchbaseConfiguration.class})
public class MachineStateTestConfiguration
22
INTEGRATION TESTS
Spring Boot “arsenal”:
o @WebMvcTest
Microservice
External
services
23
EXAMPLE CONTROLLER
@RestController
@RequestMapping("/machine")
public class MachineStateController {
private final MachineStateService machineStateService;
@GetMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public RestResponse<MachineState> getMachineState(@RequestParam
long userId) {
MachineState machineState = machineStateService
.getMachineState(userId);
return RestResponse.ok(machineState);
}
}
24
TESTING CONTROLLER LEVEL
private long TEST_USER_ID = 1;
@Autowired
private MockMvc mvc;
@MockBean
private MachineStateService machineStateService;
25
TESTING CONTROLLER LEVEL
@Test
public void shouldReturnMachineStateWhenGetRequestingJson() throws Exception
{
given(machineStateService.getMachineState(TEST_USER_ID))
.willReturn(new MachineState(100500, false));
mvc.perform(get("/machine?userId=1"))
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.machineId").value("100500"))
.andExpect(jsonPath("$.data.machineAvailable").value(“false"));
}
26
RUNNING TEST FOR CONTROLLER
@RunWith(SpringRunner.class)
@WebMvcTest(MachineStateController.class)
@ActiveProfiles("test")
@TestPropertySource(properties
={"embedded.couchbase.enabled=false",
"embedded.mariadb.enabled=false"})
public class MachineStateControllerIntegrationTest
27
COMPONENT TESTS
Component
tests
Integration
tests
Unit tests
External service
28
ADDING WIREMOCK DEPENDENCY
https://github.com/tomakehurst/wiremock
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>1.18</version>
<scope>test</scope>
</dependency>
29
MOCKING EXTERNAL DEPENDENCIES
{
"request":
{
"urlPattern": "/external-api/user/1/finished",
"method": "GET"
},
"response":
{
"status": 200,
"headers":
{
"Content-Type" : "application/json"
},
"body": "{"service":{"code": 0},
"data":{"finished":"true"}}"
}
}
30
SETTING UP COMPONENT TEST
public abstract class MachineBaseComponentTest {
@ClassRule
public static final WireMockClassRule externalApi = new
WireMockClassRule(7070);
@Autowired
protected WebApplicationContext context;
protected MockMvc mvc;
@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}
31
COMPONENT TEST
@Test
public void shouldReturnMachineStateWhenStateIsInRepository() throws
Exception {
mvc.perform(get("/machine?userId=1"))
.andExpect(status().is(200))
.andExpect(jsonPath("$.data.machineId").value("1"))
.andExpect(jsonPath("$.data.machineAvailable").value("true"));
}
32
RUNNING COMPONENT TESTS
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MachineApplication.class,
MachineStateComponentTests.MachineTestConfiguration.class})
@ActiveProfiles("test")
public abstract class MachineBaseComponentTest {}
33
CONCLUSIONS
Microservices needs an
additional levels of testing
Component
tests
Integration
tests
Unit tests
34
CONCLUSIONS
Integration testing with containers provides:
• Better reliability
• More configurability
• Speed of test execution
35© 2017 Playtika Ltd. All Rights Reserved
THANK YOU!
Oleksandr Romanov
twitter: @al8xr
mail: al8x.romanov@gmail.com

Integration testing for microservices with Spring Boot