Testing Microservices
Types of Testing
API Service
Stubs
Microservice Contracts
Service Client
Domain External Service
Microservice Contracts
Persistence Unit Test
Integration Test
Database Component Test
End-to-end Test
Test Pyramid
Manual
Exploratory
Selenium, Appium, Sikuli,
End to end JBehave
Component / Spring Cloud Contract, Pact,
Cucumber
Contracts
Junit, SOAPUI
Integration
Junit, Nunit, Mockito, JMock,
Unit (Solitary / Sociable) Lambda Behave
Consumer Driven
Contracts
Testing Services
Testing Approach
S1 1. Deploy all Microservices and
perform end-to-end tests
1. Simulates production
2. Real communication
S2 S3
1. Too cumbersome – deploy DB,
all services
2. Late feedback
S4 3. Environment blocked
4. Very hard to debug
Testing Services
Testing Approach
2. Mocking other Microservices in
S1
unit/integration tests
1. Fast feedback
2. No infrastructure setup
1. Stubs might be different
than the real service
2. Tests will pass but the
application might fail in
production
What do we need?
Producer
• Define contract
• Verify that API implementation is per contract – server tests
• Publish stub versions to repo – changes visible to both sides
Consumer
• Mock producer based on stub definitions – client integration
tests
• ATDD – Acceptance TDD
Consumer Driven Contracts
Consumer Client Test
Integration Test
Acceptance
Tests
Producer Repository
Spring Cloud Contract Verifier
Contract Definition Language (DSL)
• Definition either in groovy or pact format
Used to produce following resources
• JSON stub definitions
Used by client side
Test written by hand, test data provided by Spring
• Server tests
Test generated by Spring
Build script – Consumer (task-webservice)
dependencies
• testCompile("org.springframework.security:spring-security-test")
• testCompile("org.springframework.cloud:spring-cloud-starter-
contract-stub-runner")
Client tests
Create integration test
• Annotations
@RunWith(SpringRunner.class) // 1
@SpringBootTest(webEnvironment = WebEnvironment.MOCK, properties = {
"spring.cloud.discovery.enabled=false",
"spring.cloud.config.enabled=false","stubrunner.idsToServiceIds.basic-comments-webservice-
stubs=comments-webservice" }). // 2
@Import(OAuth2ClientTestConfiguration.class). // 3
@AutoConfigureStubRunner(ids={"anilallewar:basic-comments-webservice-stubs:+:stubs:9083"},
workOffline=true) // 4
@DirtiesContext // 5
Client tests
Actual test
@Test
public void testGetCommentsForTask() {
CommentCollectionResource comments =
this.commentsService.getCommentsForTask(REQUEST_TASK_ID);
Assert.assertEquals(2, comments.getTaskComments().size());
Assert.assertTrue(comments.getTaskComments().get(0).getTaskId().equals(TEST_TASK_ID));
}
Fails for 2 reasons
• Call is secured though OAuth2 access token
• Stub definition not available
Mock OAuth2 token
Create SecurityContext holding the mock access token
• https://spring.io/blog/2014/05/07/preview-spring-security-test-
method-security#withsecuritycontext
Code
• https://github.com/anilallewar/microservices-basics-spring-
boot/tree/master/task-
webservice/src/test/java/com/anilallewar/microservices/task
Run tests
Once you have the MockOAuth2 token code
• Comment the @AutoConfigureStubRunner annotation
• Run the test with ./gradlew clean build
java.lang.AssertionError: expected:<2> but was:<1>
• Circuit breaker kicked in and provided default value
Uncomment @AutoConfigureStubRunner and run the test
• Error creating bean with name 'batchStubRunner'
Build script – Producer (comments-webservice)
buildscript
• project.ext
springCloudContractVesrion = '1.1.1.RELEASE'
• dependencies
classpath "org.springframework.cloud:spring-cloud-contract-gradle-
plugin:${project.springCloudContractVesrion}”
dependencies
• testCompile('org.springframework.cloud:spring-cloud-starter-
contract-verifier')
Build script – Producer (comments-webservice)
// Setup the package which contains base classes that the spring cloud contract test case would extend
contracts {
packageWithBaseClasses = 'com.anilallewar.microservices.comments.contracts'
}
clean.doFirst {
delete "~/.m2/repository/anilallewar/basic-comments-webservice"
delete "~/.m2/repository/anilallewar/basic-comments-webservice-stubs"
}
// Setup the artifact id with which the stubs jar would be published, the group id comes from the 'group' attribute defined earlier
publishing {
publications {
mavenJava(MavenPublication) {
artifactId jar.baseName
version jar.version
from components.java
}
stubs(MavenPublication) {
artifactId "${jar.baseName}-stubs"
version jar.version
artifact verifierStubsJar
}
}
}
Contract DSL
Src/test/resources/contracts
response {
request { status 200
method 'GET' body( [
url $(consumer(regex('/comments/([0-9a- [
zA-z]+)')), producer('/comments/task11')) "taskId": "task11",
headers { "comment": "comment on task11",
accept(applicationJson()) "posted": $(consumer('2015-04-
} 23'),producer(execute('convertTimeValueToDate($it)')))
} ]]
)
headers {
contentType('application/json')
}
}
Abstract Base Class
Generated test extends the defined base class
Since the package for base classes is defined
as com.anilallewar.microservices.comments.contracts and
the contract is defined under the task/comments folder
under test/resources/contracts, the base class name is
TaskCommentsBase
Add @Ignore annotation – not a test class
Abstract base class
Inject a WebApplicationContext so that Spring dependency is
initialized correctly.
Annotations
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { CommentsApplication.class }, webEnvironment
= WebEnvironment.MOCK, properties = {
"spring.cloud.discovery.enabled=false", "spring.cloud.config.enabled=false"
})
Run tests
Run the test with ./gradlew clean build
• This will generate the server tests under /comments-
webservice/build/generated-test-
sources/contracts/org/springframework/cloud/contract/verifier
/tests
Publish the contract stub using ./gradlew
publishToMavenLocal
Once the contract is published, go to the task-webservice
folder and run ./gradlew clean build
• This time around the test should pass
Distributed Tracing
Sleuth & Zipkin
Sleuth
• Attaches unique id to request as it passes through enabled services
• Logs can be mined using aggregation tools like ELK (ElasticSearch,
Logstash and Kibana)
Zipkin
• Distributed tracing system
• Gathers timing data across Microservices – collection and lookup
Dependencies
Create a new project by visiting https://start.spring.io/
• Gradle project –> with Java and Spring Boot 1.5.4
• Group -> com.<name>.microservices
• Artifact -> tracing
• Dependencies -> Zipkin UI, Zipkin Client
Additional dependency
• compile('io.zipkin.java:zipkin-server')
Run ./gradlew clean build eclipse
Code changes
Application.properties
• server.port=9411
Alternatively you can add configuration on the centralized
configuration server
Add @EnableZipkinServer annotation on the Spring boot
application class
Add Zipkin client properties
Check below properties exists in configuration files for “api-
gateway”, “comments-webservice”, “task-webservice” and
“user-webservice”
spring:
zipkin:
baseUrl: http://localhost:9411/
sleuth:
sampler:
percentage: 1.0
sample:
zipkin:
enabled: true
View Traces
Run all the Microservices and other servers (zipkin server,
web-portal etc)
Hit http://localhost:9411/ (Zipkin base url)
UI
• Find specific trace and timing information
• Service dependency graph
Trace id is added to logs – enables distributed log analysis
Sample Trace
Contact Me
[email protected]
https://www.linkedin.com/in/anilallewar
https://github.com/anilallewar
http://www.slideshare.net/anilallewar
@anilallewar