Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing the Spring Framework

Testing the Spring Framework

Guest lecture to first year CS students at TU Delft.

Stéphane Nicoll

June 20, 2019
Tweet

More Decks by Stéphane Nicoll

Other Decks in Technology

Transcript

  1. © Copyright 2019 Pivotal Software, Inc. All rights Reserved. Testing

    the Spring Framework Stéphane Nicoll - @snicoll
  2. 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) - [email protected] • Based in Liège, Belgium (come over, we have waffles)
  3. 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) - [email protected] • Based in Liège, Belgium (come over, we have waffles)
  4. 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
  5. 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
  6. And we had to configure the infrastructure ourselves <?xml version="1.0"

    encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- Placeholder config --> <context:property-placeholder location="classpath:database.properties" /> <jpa:repositories base-package="com.example.cfp.domain"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="com.example.cfp.domain"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> </props> </property> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="true"/> <property name="generateDdl" value="true"/> <property name="databasePlatform" value="org.hibernate.dialect.PostgreSQL9Dialect"/> </bean> </beans>
  7. And we had to configure the infrastructure ourselves <?xml version="1.0"

    encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- Placeholder config --> <context:property-placeholder location="classpath:database.properties" /> <jpa:repositories base-package="com.example.cfp.domain"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="com.example.cfp.domain"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> </props> </property> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="true"/> <property name="generateDdl" value="true"/> <property name="databasePlatform" value="org.hibernate.dialect.PostgreSQL9Dialect"/> </bean> </beans>
  8. And chose which libraries to use (and their versions!) <dependency>

    <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.3.0.RELEASE</version> </dependency>
  9. And chose which libraries to use (and their versions!) <dependency>

    <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>1.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.1.9.Final</version> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> <scope>runtime</scope> </dependency>
  10. And chose which libraries to use (and their versions!) <dependency>

    <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>1.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.1.9.Final</version> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> <scope>runtime</scope> </dependency>
  11. 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
  12. 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); } }
  13. 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); } }
  14. 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(); } }
  15. 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(); } }
  16. 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()); } }
  17. 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); ... }); } }
  18. 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
  19. ApplicationContextRunner example @Test void shouldFailWhenNativeTypesAreNotAvailable() { this.contextRunner .run((context) -> {

    assertThat(context).hasFailed(); assertThat(context.getStartupFailure()) .hasRootCauseInstanceOf( NativeTypesNotAvailableException.class); }); }
  20. 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); }); }
  21. 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); }); }
  22. 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); }); }
  23. 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() { ... } }
  24. 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