Slide 1

Slide 1 text

© Copyright 2019 Pivotal Software, Inc. All rights Reserved. Testing the Spring Framework Stéphane Nicoll - @snicoll

Slide 2

Slide 2 text

Who am I? ● Software Engineer at Pivotal (Spring Boot, Spring Framework and start.spring.io) ● Involved in open source for 15 years (Apache Maven, Spring Framework) ● snicoll on the Web (Twitter, Github) - snicoll@pivotal.io ● Based in Liège, Belgium (come over, we have waffles)

Slide 3

Slide 3 text

Who am I? ● Software Engineer at Pivotal (Spring Boot, Spring Framework and start.spring.io) ● Involved in open source for 15 years (Apache Maven, Spring Framework) ● snicoll on the Web (Twitter, Github) - snicoll@pivotal.io ● Based in Liège, Belgium (come over, we have waffles)

Slide 4

Slide 4 text

Prerequisite “

Slide 5

Slide 5 text

Prerequisite “They have a good notion of Java and OOP, know how to use IntelliJ, are used to building tools such as Maven and Gradle, and know JUnit 5 very well. Maurício Aniche

Slide 6

Slide 6 text

Prerequisite “They have a good notion of Java and OOP, know how to use IntelliJ, are used to building tools such as Maven and Gradle, and know JUnit 5 very well. Maurício Aniche

Slide 7

Slide 7 text

This is what I was doing during my 1st year

Slide 8

Slide 8 text

This is what I was doing during my 1st year

Slide 9

Slide 9 text

Once upon a time…

Slide 10

Slide 10 text

We stored libs in source control

Slide 11

Slide 11 text

We stored libs in source control

Slide 12

Slide 12 text

The power of convention over configuration

Slide 13

Slide 13 text

And we had to configure the infrastructure ourselves ${hibernate.dialect} ${hibernate.hbm2ddl.auto} ${hibernate.show_sql} ${hibernate.format_sql}

Slide 14

Slide 14 text

And we had to configure the infrastructure ourselves ${hibernate.dialect} ${hibernate.hbm2ddl.auto} ${hibernate.show_sql} ${hibernate.format_sql}

Slide 15

Slide 15 text

And chose which libraries to use (and their versions!) org.springframework spring-context ${spring.version} org.springframework spring-orm ${spring.version} org.springframework.data spring-data-jpa 1.3.0.RELEASE

Slide 16

Slide 16 text

And chose which libraries to use (and their versions!) org.hibernate.javax.persistence hibernate-jpa-2.0-api 1.0.1.Final org.hibernate hibernate-entitymanager 4.1.9.Final runtime mysql mysql-connector-java 5.1.6 runtime

Slide 17

Slide 17 text

And chose which libraries to use (and their versions!) org.hibernate.javax.persistence hibernate-jpa-2.0-api 1.0.1.Final org.hibernate hibernate-entitymanager 4.1.9.Final runtime mysql mysql-connector-java 5.1.6 runtime

Slide 18

Slide 18 text

The power of convention over configuration

Slide 19

Slide 19 text

Live Coding Enough with the slides already!

Slide 20

Slide 20 text

Summary ● Conventions can significantly improve your productivity ● Best practices for teams and the ecosystem at large ● Provide sane defaults when you don’t or shouldn’t care ● Make upgrade and maintenance easier ● …. but shouldn’t get in your way ● Built on top of an API you could use to implement something slightly or totally different ● Can be customised without having you to redefine everything ● Depending on the scope, not everything can be the target of a convention

Slide 21

Slide 21 text

See you in 15 Break

Slide 22

Slide 22 text

Why bother testing at all?

Slide 23

Slide 23 text

Testing to reduce risk Risk None YOLO Tests 0 Lots

Slide 24

Slide 24 text

Demo CI/CD in action on start.spring.io

Slide 25

Slide 25 text

Unit Tests

Slide 26

Slide 26 text

Unit tests should not require any “framework” @Service public class SubmissionService { @Autowired private SubmissionRepository submissions; @Transactional public Submission create(SubmissionRequest request) { Submission submission = new Submission(); ... return this.submissions.save(submission); } }

Slide 27

Slide 27 text

Unit tests should not require any “framework” @Service public class SubmissionService { private final SubmissionRepository submissions; public SubmissionService(SubmissionRepository submissions) { this.submissions = submissions; } @Transactional public Submission create(SubmissionRequest request) { Submission submission = new Submission(); ... return this.submissions.save(submission); } }

Slide 28

Slide 28 text

Unit testing a client for an HTTP API class SpringBootMetadataReaderTests { private final RestTemplate restTemplate = new RestTemplate(); private final MockRestServiceServer server = MockRestServiceServer.bindTo( this.restTemplate).build(); @Test void readAvailableVersions() throws IOException { this.server.expect(requestTo("https://spring.io/project_metadata/spring-boot")) .andRespond(withSuccess(new ClassPathResource( "metadata/sagan/spring-boot.json"), MediaType.APPLICATION_JSON)); ... this.server.verify(); } }

Slide 29

Slide 29 text

Integration Tests

Slide 30

Slide 30 text

@SpringBootTest

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

@JsonTest @WebMvcTest @WebFluxTest @DataJpaTest @JdbcTest @JooqTest @DataMongoTest @DataNeo4jTest @DataRedisTest @DataLdapTest @RestClientTest

Slide 33

Slide 33 text

Slice test example @RestClientTest(SpringBootMetadataReader.class) class SpringBootMetadataReaderTests { @Autowired private SpringBootMetadataReader springBootMetadataReader; @Autowired private MockRestServiceServer server; @Test void readAvailableVersions() throws IOException { this.server.expect(requestTo("https://spring.io/project_metadata/spring-boot")) .andRespond(withSuccess(new ClassPathResource( "metadata/sagan/spring-boot.json"), MediaType.APPLICATION_JSON)); ... this.server.verify(); } }

Slide 34

Slide 34 text

Slice test example with @MockBean @WebMvcTest(controllers = CfpController.class) class CfpControllerTest { @Autowired private MockMvc mvc; @MockBean private SubmissionService submissionService; @Test void submitTalk() throws Exception { given(this.submissionService.create(any())).willReturn(new Submission()); this.mvc.perform(post("/submit") .param("title", "Alice in Wonderland") .param("summary", "my abstract")) .andExpect(status().isFound()) .andExpect(header().string(HttpHeaders.LOCATION, "/submit?navSection=submit")); verify(this.submissionService).create(any()); } }

Slide 35

Slide 35 text

https://www.testcontainers.org

Slide 36

Slide 36 text

testcontainers example @DisabledWithoutDockerTestcontainers public class ReactiveRestClientAutoConfigurationTests { @Container static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(); private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( ReactiveRestClientAutoConfiguration.class)); @Test void restClientCanQueryElasticsearchNode() { String endpoint = elasticsearch.getContainerIpAddress() + ":" + elasticsearch.getFirstMappedPort(); this.contextRunner.withPropertyValues( "spring.data.elasticsearch.client.reactive.endpoints=" + endpoint) .run((context) -> { ReactiveElasticsearchClient client = context.getBean( ReactiveElasticsearchClient.class); ... }); } }

Slide 37

Slide 37 text

Testing Spring Boot

Slide 38

Slide 38 text

ApplicationContextRunner ● Test utility to prepare a mini ApplicationContext suitable for a very precise scenario: ● Specific auto-configuration(s) ● User configuration, if any ● Environment tuning (properties customization) ● System properties handling ● Starts/Stop the context automatically with a ContextConsumer callback ● AssertJ style assert ● hasSingleBean, doesNotHaveBean, hasFailed ● getBean, getBeans, etc

Slide 39

Slide 39 text

ApplicationContextRunner example @Test void shouldFailWhenNativeTypesAreNotAvailable() { this.contextRunner .run((context) -> { assertThat(context).hasFailed(); assertThat(context.getStartupFailure()) .hasRootCauseInstanceOf( NativeTypesNotAvailableException.class); }); }

Slide 40

Slide 40 text

ApplicationContextRunner example @Test void shouldFailWhenNativeTypesAreNotAvailable() { this.contextRunner .withConfiguration(AutoConfigurations.of( Neo4jDataAutoConfiguration.class, TransactionAutoConfiguration.class)) .run((context) -> { assertThat(context).hasFailed(); assertThat(context.getStartupFailure()) .hasRootCauseInstanceOf( NativeTypesNotAvailableException.class); }); }

Slide 41

Slide 41 text

ApplicationContextRunner example @Test void shouldFailWhenNativeTypesAreNotAvailable() { this.contextRunner .withConfiguration(AutoConfigurations.of( Neo4jDataAutoConfiguration.class, TransactionAutoConfiguration.class)) .withPropertyValues( "spring.data.neo4j.uri=bolt://localhost:7687", "spring.data.neo4j.use-native-types:true") .run((context) -> { assertThat(context).hasFailed(); assertThat(context.getStartupFailure()) .hasRootCauseInstanceOf( NativeTypesNotAvailableException.class); }); }

Slide 42

Slide 42 text

ApplicationContextRunner example @Test void shouldFailWhenNativeTypesAreNotAvailable() { this.contextRunner .withConfiguration(AutoConfigurations.of( Neo4jDataAutoConfiguration.class, TransactionAutoConfiguration.class)) .withPropertyValues( "spring.data.neo4j.uri=bolt://localhost:7687", "spring.data.neo4j.use-native-types:true") .withClassLoader(new FilteredClassLoader( "org.neo4j.ogm.drivers.bolt.types")) .run((context) -> { assertThat(context).hasFailed(); assertThat(context.getStartupFailure()) .hasRootCauseInstanceOf( NativeTypesNotAvailableException.class); }); }

Slide 43

Slide 43 text

Classpath tuning @RunWith(ModifiedClassPathRunner.class) @ClassPathOverrides("javax.servlet:servlet-api:2.5") public class NoSuchMethodFailureAnalyzerTests { @Test public void noSuchMethodErrorIsAnalyzed() { Throwable failure = createFailure(); assertThat(failure).isNotNull(); FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer() .analyze(failure); assertThat(analysis).isNotNull(); ... } private Throwable createFailure() { ... } }

Slide 44

Slide 44 text

Links ● https://spring.io/projects/spring-boot ● https://start.spring.io ● https://spring.io/guides ● https://concourse-ci.org/ ● Testing Spring Boot Applications (Andy Wilkinson @ Spring I/O 2019): https:// www.youtube.com/watch?v=5sjFn9BsAds

Slide 45

Slide 45 text

© Copyright 2019 Pivotal Software, Inc. All rights Reserved. Thank you! @snicoll