Download - Testing Java Microservices Devoxx be 2017
Testing Java MicroservicesFrom Unit to Deployment TestsAlex Soto@alexsotob
Andy Gumbrecht@AndyGeeDe
@alexsotob @AndyGeeDe2
Alex Soto
Red Hat Engineer www.lordofthejars.com@alexsotob
Who Am I?
@alexsotob @AndyGeeDe3
Andy Gumbrecht
Tomitribe Consultant Developerwww.tomitribe.com
Apache TomEE PMCTomee.apache.org
@AndyGeeDeAlso a dad
Who Am I?
@alexsotob @AndyGeeDe4
https://www.manning.com/books/testing-java-microservices
Oops! Shameless plug alert
@alexsotob @AndyGeeDe5
What Are Microservices?
@alexsotob @AndyGeeDe6
Anatomy of Microservice
http://microprofile.io
https://projects.eclipse.org/proposals/eclipse-microprofile
@alexsotob @AndyGeeDe7
Resources
Domain
Controllers
RepositoriesGatew
ays
External Service
@alexsotob @AndyGeeDe8
Testing Evolution
Manual Tests
After Code Automatic
Test
Test First TDD and BDD
Service Virtualization
and CDC
Testing in Production
@alexsotob
Unit
Component
DeploymentIntegration
Contract
What does this mean in terms of testing?Testing Strategies
9
Production
@alexsotob @AndyGeeDe10
@alexsotob @AndyGeeDe11
@alexsotob @AndyGeeDe12
Questions
@alexsotob @AndyGeeDe13
Unit Testing
@alexsotob @AndyGeeDe14
@alexsotob @AndyGeeDe15
Test Doubles
@alexsotob
DummiesPassed but not used
FakesImplementation with shortcuts
StubsProvide predefined answers
MocksPre-programmed expectations,
SpiesRecord information
Test DoublesKind of test doubles
16
@alexsotob @AndyGeeDe
JUnit Test
@Test
public void should_find_composer_by_name() {
// Given:
Composers composers = new Composers();
// When:
final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart");
// Then:
assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);
}
17
Readable name
BDD style
Assertions
@alexsotob @AndyGeeDe18
Questions
@alexsotob19
AssertJ
@alexsotob @AndyGeeDe
AssertJ Test
import static org.assertj.core.api.Assertions.assertThat;
@Test
public void should_find_composer_by_name() {
// Given:
Composers composers = new Composers();
// When:
final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart”);
// Then:
assertThat(mozart.getName()).isEqualTo("Wolfgang Amadeus Mozart”);
assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);
assertThat(mozart.getBirthdate()).isEqualTo(LocalDate.of(1756, 1, 27));
assertThat(mozart).isEqualToComparingFieldByField(expectedMozart);
assertThat(mozart).isEqualToIgnoringNullFields(expectedMozart);
}
20
Static Import
Readable Assertions
Not Depending on Equals
@alexsotob @AndyGeeDe
AssertJ Test Collections@Test
public void should_find_operas_by_composer_name() {
// Given:
Composers composers = new Composers();
// When:
final List<Opera> operas = composers.findOperasByComposerName("Wolfgang Amadeus Mozart");
// Then:
assertThat(operas)
.hasSize(2)
.extracting(Opera::getName)
.containsExactlyInAnyOrder("Die Zauberflöte", "Don Giovanni”);
}
21
Chained method (IDE support)
Creates a list of getName results
Methods for String
@alexsotob @AndyGeeDe
AssertJ Soft Assertions@Test
public void should_find_composer_by_name_soft_assertions() {
// Given:
Composers composers = new Composers();
// When:
final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart");
// Then:
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(mozart.getName()).isEqualTo("Wolfgang Amadeus Mozart");
softly.assertThat(mozart.getEra()).isEqualTo(Era.CLASSICAL);
softly.assertThat(mozart.getBirthdate()).isEqualTo(LocalDate.of(1756, 1, 27));
softly.assertThat(mozart.getDied()).isEqualTo(LocalDate.of(1791, 12, 5));
});
}
22
Java 8 Lambda
All assertions in Block
@alexsotob @AndyGeeDe
AssertJ Exceptions@Test
public void should_throw_exception_if_composer_not_found_version_3() {
// Given:
Composers composers = new Composers();
// When:
Throwable thrown = catchThrowable(() -> composers.findComposerByName("Antonio Salieri"));
// Then:
assertThat(thrown).isInstanceOf(IllegalArgumentException.class)
.withFailMessage("Composer Antonio Salieri is not found”);
}
23
Catch Exception of Lambda
Assertion Methods for Exceptions
@alexsotob @AndyGeeDe24
Questions
@alexsotob @AndyGeeDe25
@alexsotob @AndyGeeDe
Init Mocks
@RunWith(MockitoJUnitRunner.class)
public class Test {
@Mock EmailService email;
}
public class Test {
@Mock EmailService email;
@Before public void before() {
MockitoAnnotations.initMocks(this)
}
}
@Rule public MockitoRule mockito = MockitoJUnit.rule();
EmailService emailService = Mockito.mock(EmailService.class);
26
Using JUnit Runner
Defines Mock
Init programmatically
Without annotation
With JUnit Rule
@alexsotob @AndyGeeDe
Using Mocks@Mock EmailService email;
Mockito.when(email.receiveBodyMessagesWithSubject("My Subject”))
.thenReturn("This is My Message")
Mockito.when(email.receiveBodyMessagesWithSubject(Matchers.anyString()))
.thenReturn(“This is My Message");
27
Record expectation
Concrete Parameter
Any parameter
@alexsotob @AndyGeeDe
Verifying Mocks
@Mock EmailService email;
InvoiceService invoiceService = new InvoiceService(email);
invoiceService.send();
Mockito.verify(email, Mockito.times(1))
.sendMessage(Matchers.anyString(), Matchers.anyString());
28
Mock used as collaborator
Verify mocked method is called once
@alexsotob @AndyGeeDe
Capturing Arguments Mocks@Mock EmailService email;
InvoiceService invoiceService = new InvoiceService(email);
invoiceService.send();
verify(email, times(1))
.sendMessage(
argThat(subject -> subject.startsWith(“Invoice:")),
Matchers.anyString()
);
29
Mock used as collaborator
Verify content first param value of sendMessage
@alexsotob @AndyGeeDe
Bonus Track (JUnit 5)@ExtendWith(MockitoExtension.class)
class MyMockitoTest {
@BeforeEach
void init(@Mock Person person) {
when(person.getName()).thenReturn("Dilbert");
}
@Test
void simpleTestWithInjectedMock(@Mock Person person) {
assertThat(person.getName()).isEqualTo("Dilbert");
}
}
30
Mockito Extension
Initialize Mock before each test
Use injected Mock
@alexsotob @AndyGeeDe
DEMO
31
@alexsotob @AndyGeeDe32
Questions
@alexsotob @AndyGeeDe33
Component Testing
@alexsotob @AndyGeeDe
Arquillian@RunWith(Arquillian.class)
public class ColorServiceTest {
@Deployment(testable = false)
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class).addClasses(ColorService.class, Color.class);
}
@ArquillianResource
private URL webappUrl;
@Test
public void getColorObject() throws Exception {
final WebClient webClient = WebClient.create(webappUrl.toURI());
final Color color =
webClient.accept(MediaType.APPLICATION_JSON).path("color/object").get(Color.class);
}
}
34
Arquillian Runner
WAR Deployment
Injects the application URL
Real REST call
@alexsotob @AndyGeeDe
Spring Boot@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
@Autowired
Invoice invoice;
@Test
public void should_generate_invoice_report() {
}
}
35
Spring Runner
Mock Servlet Runner
Inject Invoice
@alexsotob @AndyGeeDe
Spring Boot REST@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortExampleTests {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
int port;
@Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
// assertions
}
}
36
Spring Runner Starts Embedded Servlet Runner
REST calls to running server
Port where servlet is started
Executes REST call
@alexsotob @AndyGeeDe
Wildfly Swarm
@RunWith(Arquillian.class)
@DefaultDeployment
public class InContainerTest {
@Inject
private Invoice invoice;
@Test
public void should_generate_an_invoice() {
}
}
37
Arquillian Runner
Create the deployment of the entire application.
CDI injects Invoice and its deps
@alexsotob @AndyGeeDe
Vert.X@RunWith(VertxUnitRunner.class)
public class CalculatorVerticleTest {
@Rule
public RunTestOnContext rule = new RunTestOnContext();
@Before
public void before(TestContext context) throws IOException {
rule.vertx().deployVerticle(CalculatorVerticle.class.getName(), options,
context.asyncAssertSuccess());
}
@After
public void after(TestContext context) {
rule.vertx().close(context.asyncAssertSuccess());
}
@Test
public void should_add_two_numbers(TestContext context) {}
38
VertX runner
Deploy Verticle under test
Shutdown VertX server
@alexsotob @AndyGeeDe
DEMO
39
https://github.com/tomitribe/tomee-jaxrs-starter-project
@alexsotob @AndyGeeDe40
Questions
@alexsotob41
REST API
@alexsotob @AndyGeeDe42
@alexsotob @AndyGeeDe
REST-assured Example
@Test
public void should_find_composer() {
given()
.when()
.get("{composer}", "Ludwig van Beethoven")
.then()
.assertThat()
.body("name", is("Ludwig van Beethoven"))
.body("operas.size()", is(1))
.body("operas.name", hasItems("Fidelio"));
}
43
GET Http Method with Placeholders
GPath Expression
@alexsotob @AndyGeeDe
REST-assured Request Specification
.get("http://example.com/{composer}", "Ludwig van Beethoven")
RequestSpecBuilder builder = new RequestSpecBuilder();
builder.setBaseUri("http://example.com");
given().spec(builder.build())...
44
Use domain directly
Use Spec Builder
Reuse Everywhere
@alexsotob @AndyGeeDe
REST-assured Auth
given().auth().oauth2(accessToken).when()...
given().auth().form("John", "Doe", springSecurity().withCsrfFieldName("_csrf")).when()...
given().auth().basic("username", "password").when()...
45
@alexsotob @AndyGeeDe
Custom ParsersNot just JSON and XML
SSL Support.relaxedHTTPSValidation()
FiltersInput/Output modification
JSON Schema ValidationContent not Important
More Features
of Rest-Assured
46
@alexsotob @AndyGeeDe
DEMO
47
@alexsotob @AndyGeeDe48
Questions
@alexsotob49
Service Virtualization
@alexsotob
Service Virtualization Capture Mode
50
Service A External Network Service B
Scripts
Proxy
@alexsotob
Service Virtualization Simulate Mode
51
Service A External Network Service B
Scripts
Proxy
@alexsotob @AndyGeeDe52
@alexsotob @AndyGeeDe
Hoverfly Example@ClassRule
public static HoverflyRule hoverflyRule =
HoverflyRule.inCaptureOrSimulationMode("getcomposers.json");
@Test
public void should_get_composers_from_composers_microservice() {
// Given:
ComposersGateway composersGateway = new ComposersGateway("operas.com", 8081);
// When:
Composer composer = composersGateway.getComposer("Ludwig van Beethoven");
// Then:
assertThat(composer.getName()).isEqualTo("Ludwig van Beethoven");
}
53
Start Hoverfly
Use Real Host
Get Data from Real
Proxied data is stored
@alexsotob @AndyGeeDe
Hoverfly Example{
"data" : {
"pairs" : [{
"request" : {
"path" : "/Ludwig van Beethoven",
"method" : "GET",
"destination" : “operas.com:8081",
...
}
"response" : {
"status" : 200,
"body" : "{\"name\":\"Ludwig van Beethoven\",}",
"encodedBody" : false,
"headers" : {
"Connection" : [ "keep-alive" ],
...
}
}
}
}
54
@alexsotob @AndyGeeDe
Simulate Mode
@ClassRule
public static HoverflyRule hoverflyRule =
HoverflyRule.inSimulationMode(classpath("simulation.json"));
SimulationSource.dsl(
service("api.flight.com")
.get("/api/bookings/1")
.willReturn(success("{\"bookingId\":\"1\"\}", "application/json"))
)
55
Hoverfly JUnit Rule
Loads from Hoverfly format Json
Build Request/Responses Programmatically
@alexsotob @AndyGeeDe
Verification
hoverfly.verify(
service(matches("*.flight.*"))
.get("/api/bookings")
.anyQueryParams());
//
hoverfly.verify(
service("api.flight.com")
.put("/api/bookings/1")
.anyBody()
.header("Authorization", "Bearer some-token"), times(2));
56
Verify exactly one request
Verify exactly two requests
@alexsotob @AndyGeeDe
DEMO
57
@alexsotob @AndyGeeDe58
Questions
@alexsotob @AndyGeeDe59
Integration Tests
@alexsotob @AndyGeeDe60
Resources
Domain
Controllers
RepositoriesGatew
ays
External Service
@alexsotob61
Persistence Tests
@alexsotob @AndyGeeDe
First attempt@Test
public void should_find_composer_by_name() {
// Given:
clearDatabase(jdbcUri);
insertComposersData(jdbcUri);
ComposersRepository composersRepository = new ComposersRepository();
// When:
Composer mozart = composersRepository.findComposerByName("Wolfgang Amadeus Mozart");
// Then:
assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);
}
62
Prepare Database
Execute Query
@alexsotob63
APE
@alexsotob @AndyGeeDe
APE SQL example@Rule
public ArquillianPersistenceRule arquillianPersistenceRule =
new ArquillianPersistenceRule();
@DbUnit
@ArquillianResource
RdbmsPopulator rdbmsPopulator;
@Before
public void populateData() {
// Given:
rdbmsPopulator.forUri(jdbcUri).withDriver(Driver.class).withUsername("sa")
.withPassword("").usingDataSet("composers.yml")
.execute();
}
64
APE JUnit Rule (not necessary with Arquillian
Runner)
Set DBUnit usage
Configure Connection and Dataset
Populate
@alexsotob @AndyGeeDe
APE SQL example
@After
public void clean_database() {
// Given:
rdbmsPopulator.forUri(jdbcUri).withDriver(Driver.class).withUsername("sa")
.withPassword("").usingDataSet("composers.yml")
.clean();
}
65
Clean After Test
Clean Database
@alexsotob
Boilerplate Code
Programmatic/Declarative@UsingDataSet/@ShouldMatchDataSet
SQL SupportDBUnit and Flyway
REST API SupportPostman Collections
NoSQL SupportMongoDB, Couchbase, CouchDB, Vault, Redis, Infinispan
Benefits of Arquillian APE
66
@alexsotob @AndyGeeDe
DEMO
67
@alexsotob @AndyGeeDe68
Questions
@alexsotob @AndyGeeDe69
@alexsotob @AndyGeeDe
@alexsotob @AndyGeeDe71
@alexsotob @AndyGeeDe
Arquillian Cube DSL
@RunWith(SpringRunner.class)
@SpringBootTest(classes = PingPongController.class, webEnvironment = RANDOM_PORT)
@ContextConfiguration(initializers = PingPongSpringBootTest.Initializer.class)
public class PingPongSpringBootTest {
@ClassRule
public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
.withPortBinding(6379);
@Autowired
TestRestTemplate restTemplate;
@Test
public void should_get_data_from_redis() {
}
72
Spring Boot Test
Custom Initializer
Container DefinitionSets Container Environment
@alexsotob @AndyGeeDe
Arquillian Cube Example@RunWith(Arquillian.class)
public class HelloWorldTest {
@ArquillianResource
@DockerUrl(containerName = "helloworld", exposedPort = 8080)
RequestSpecBuilder requestSpecBuilder;
@Test
public void should_receive_ok_message() {
RestAssured
.given().spec(requestSpecBuilder.build())
.when().get().then()
.assertThat().body("status", equalTo("OK"));
}
}
73
Arquillian Runner
REST-Assured Integration Environment Resolver
Normal REST-Assured Call
src/test/docker/docker-compose.yml
@alexsotob @AndyGeeDe
DEMO
74
@alexsotob @AndyGeeDe75
Questions
@alexsotob @AndyGeeDe76
Micro Services architecture
@alexsotob77
Villains Database Application
Consumer V1 Producer V1GET /crimes/Gru
[{ "name": "Moon", "villain": "Gru", "wiki": "/wiki/Moon"}]
GET /villains/Gru
"name": “Gru”,"areaOfInfluence": "World""crimes": [{ "name": "Moon", "wiki": "/wiki/Moon" }]
X
XV1.1
@alexsotob @AndyGeeDe78
Contract Tests
@alexsotob79
Consumer Side
Stub Server
ConsumerTest
Expectations
GET /crimes/Gru
[{"name":...]}
Sharing
Provider Side
Sharing
Test
Provider
GET /crimes/Gru
[{"name":...]}
@alexsotob81
> Pact FoundationPact specification v3
> Integration with several languagesJVM, Ruby, Python, Go, .Net, Swift, JS
> Pact BrokerSharing contracts, API documentation, Overview of services
> Arquillian AlgeronArquillian ecosystem + Pact, Publishers/Retrievers, JBoss Forge
@alexsotob @AndyGeeDe
Consumer Side@RunWith(Arquillian.class)
public class CrimesConsumerContractTest {
@StubServer
URL pactServer;
@Pact(provider = "crimes", consumer = "villains")
public RequestResponsePact returnListOfCrimes(PactDslWithProvider builder) {
return builder
.uponReceiving("Gru villain to get all Crimes")
.path("/crimes/Gru").method("GET").willRespondWith()
.status(200).body(RESPONSE)
.toPact();
}
@Test
@PactVerification(“crimes”)
public void should_get_list_of_crimes_by_villain() {}
82
Arquillian Runner
Location of Stub Server
Record expectations between C/P
Test execution and contract creation
@alexsotob @AndyGeeDe
Provider Side
@RunWith(Arquillian.class)
@Provider("crimes")
@ContractsFolder("~/crimescontract")
public class CrimesContractTest {
@ArquillianResource
Target target;
@Test
public void should_validate_contract() {
assertThat(target).withUrl(getCrimesServer()).satisfiesContract();
}
}
83
Arquillian Runner
Location of Contract
Http client player
Verification of real responses against contract
@alexsotob84
DEMO
@alexsotob @AndyGeeDe85
Questions
@alexsotob86
Deployment Tests
@alexsotob @AndyGeeDe87
@alexsotob @AndyGeeDe
Deployment Test@RunWith(ArquillianConditionalRunner.class)
@RequiresKubernetes
public class HelloWorldIT {
@ArquillianResource
@RouteURL("kubernetes-hello-world")
URL url;
@Test
public void service_should_be_accessible() throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().get().url(url).build();
Response response = client.newCall(request).execute();
assertThat(response.isSuccessful()).isTrue();
}
}
88
Arquillian Runner
Precondition in test
URL for access to service
Verifies connection is possible
@alexsotob89
DEMO
@alexsotob @AndyGeeDe90
Questions
@alexsotob91
Continuous Delivery
@alexsotob92
Smart Testing
@alexsotob93
Production Sources Tests
https://martinfowler.com/articles/rise-test-impact-analysis.html
@alexsotob
Smart Testing Maven Extension
curl -sSL https://git.io/v5jy6 | bash
94
Installs extension
mvn clean test -Dsmart.testing="new, changed, affected"
Runs only new, changed and prod related tests
mvn clean test -Dsmart.testing.mode=ordering -Dsmart.testing="new, changed, affected”
Prioritize new, changed and prod related tests
@alexsotob @AndyGeeDe95
We are on Production
@alexsotob @AndyGeeDe96
Shit! We break Production Environment
@alexsotob @AndyGeeDe97
Blue-Green Deployments
Starts with a “git commit and git push”
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
BUILDSCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
BUILDSCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
BUILDSCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
BUILDSCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
BUILDSCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
CLUSTER
Blue/Green Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
CLUSTER
@alexsotob @AndyGeeDe105
Questions
@alexsotob106
Canary Release
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
@alexsotob @AndyGeeDe117
Questions
@alexsotob @AndyGeeDe118
Dark Launches
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
INTERNAL USERSSERVICE
Canary Deployment
DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS
SCM
SERVICE
@alexsotob121
DEMO
@alexsotob @AndyGeeDe122
Lessons Learnt
@alexsotob
Unit
Component
DeploymentIntegration
Contract
What does it means in terms of Tests?Testing Strategies
123
Production
@alexsotob @AndyGeeDe124
Tests are a Team
@alexsotob @AndyGeeDe125
Secure Your Steps
@alexsotob @AndyGeeDe126
Automate Everything
@alexsotob @AndyGeeDe127
“Change is the essential process of all of existence.”
—Spock
@alexsotob @AndyGeeDe128
Questions
@alexsotob @AndyGeeDe
Useful Linkshttps://github.com/lordofthejars/composers-unicorn
https://github.com/lordofthejars/musicboxhttps://github.com/arquillian-testing-microservices/villains-service/tree/contract_testinghttps://github.com/arquillian-testing-microservices/crimes-service/tree/contract_testinghttps://github.com/arquillian/arquillian-extension-persistence/tree/2.0.0/arquillian-ape-nosqlhttps://github.com/lordofthejars/rest-springboot-openshift