Spring Framework Testing Concepts: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(36 intermediate revisions by the same user not shown)
Line 14: Line 14:
=Unit Tests=
=Unit Tests=


According to Spring reference documentation, a Spring unit test is a test that does not use a real application context. The unit test should work without such a real application context, and use objects created with the <tt>new</tt> operator and injected with a direct invocation of the constructor or setter methods.  
According to Spring reference documentation, a Spring unit test is a test that does not use a real application context. The unit test should work without such a real application context, and use objects created with the <tt>new</tt> operator and injected with a direct invocation of the constructor or setter methods. Spring provides <tt>MockEnvironment</tt> and <tt>MockPropertySource</tt> that are useful for developing out-of-container tests. <font color=darkgray>Understand this</font>.
 
Spring provides <tt>MockEnvironment</tt> and <tt>MockPropertySource</tt> that are useful for developing out-of-container tests. <font color=darkgray>Understand this</font>.  


=Integration Tests=
=Integration Tests=


According to Spring reference documentation, a Spring integration test is a test where dependencies are injected with the help of a real application context, albeit a special, test-oriented implementation of it. Yet, these tests do not rely on the presence of an application server or Spring Boot instance, or a deployment environment. These tests are slower to run than unit tests, but much faster than the equivalent Selenium or remote tests, that rely on deployment in an actual deployment environment.  
According to Spring reference documentation, a Spring integration test is a test where dependencies are injected with the help of a real application context, albeit a special, test-oriented implementation of it. Yet, these tests do not rely on the presence of an application server, a Spring Boot instance or a deployment environment. These tests are slower to run than unit tests, but much faster than the equivalent Selenium or remote tests, that rely on deployment in an actual deployment environment.  


Spring provides an integration testing framework, named Spring TestContext Framework. The framework is annotation-driven, and aims to provide the following:
Spring provides an integration testing framework, named '''Spring TestContext Framework'''. The framework is annotation-driven, and aims to provide the following:
* Context management and caching between tests. This generally speeds up successive tests.
* Context management and caching between tests. This generally speeds up successive tests.<font color=darkgray>Clarify if this refers to test functions (@Test) within the same test class, or refers to test functions belonging to different test classes.</font>
* Dependency injection of test fixture instances.
* Dependency injection of test fixture instances.
* Transaction management. The tests can be coded assuming existence of a transaction.
* Transaction management. The tests can be coded assuming existence of a transaction.
Line 68: Line 66:


{{External|[https://github.com/ovidiuf/playground/tree/master/spring/testing/01-spring-TestContext-framework-simplest Simplest Spring TestContext Framework Example]}}
{{External|[https://github.com/ovidiuf/playground/tree/master/spring/testing/01-spring-TestContext-framework-simplest Simplest Spring TestContext Framework Example]}}
==Activating Profiles for Integration Tests==
Tests are allowed to use a special annotation to declare what profile or profiles are [[Spring_Property_Injection_Concepts#Active_Profile|active]] when the test is executed. That annotation is [[@ActiveProfiles]].
<syntaxhighlight lang='java'>
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {...})
@ActiveProfiles("heavy")
public class HeavyIntegrationTest {
  ...
}
</syntaxhighlight>
The above declaration says that the "heavy" profile will be active while HeavyIntegrationTest is executed.
===Playground Example===
{{External|[https://github.com/ovidiuf/playground/tree/master/spring/testing/03-active-profiles-in-integration-tests Playground Activating Profiles for Integration Tests Example]}}


==Annotations==
==Annotations==


* [[@ContextConfiguration]]
* [[@ContextConfiguration]]
* [[@ActiveProfiles]]
* [[@TestPropertySource]]
* [[@DirtiesContext]]


=Test sdout and stderr=
=<span id='Output_Test_stdout_and_stderr_to_Console'></span>Test Logging=


When building with Gradle, stdout and stderr of the tests being executed will be shown at the console if the following are configured:
When building with Gradle, stdout and stderr of the tests being executed will be shown at the console if the following are configured:
* Set <tt>testLogging.showStandardStreams</tt> of the Java plugin Test task, as shown here: [[Testing_with_Gradle_Java_Plugin#Show_stdout_and_stderr_of_the_Test_JVM_on_the_Console|Show stdout and stderr of the Test JVM on the Console]]
* Set <tt>testLogging.showStandardStreams</tt> of the Java plugin Test task, as shown here: [[Testing_with_Gradle_Java_Plugin#Show_stdout_and_stderr_of_the_Test_JVM_on_the_Console|Show stdout and stderr of the Test JVM on the Console]].
* Make sure that a slf4j binding is in the test classpath. Spring uses Logback by default, so the following will work: [[Slf4j#Logback_Binding|Place Logback Binding on test classpath]].
* Place a [[Logback.xml#Example|logback.xml]] configuration in src/test/resources. At the time of the writing, I was not able to figure out why logging configuration from src/test/resources/application.yml did not work.
 
=Using Property Configuration Holders with Plain Spring TestContext Framework=
 
Property configuration holders are a Spring Boot pattern. They require Spring Boot dependencies and runtime to function. However, they're useful, so this is a method to get them to work with plain TestContext Framework tests:
 
{{Internal|Using Property Configuration Holders with Plain Spring TestContext Framework|Using Property Configuration Holders with Plain Spring TestContext Framework}}
 
=Concurrent Test Execution=
 
{{External|https://www.baeldung.com/spring-5-concurrent-tests}}
 
Concurrent test execution is supported by Spring Test Context Framework that comes with Spring 5. JUnit 4 or newer is needed.
 
When Gradle is used to run the build, the tests are executed by default in a separate (forked) JVM. More than one JVMs dedicated to testing can be configured with [[Testing_with_Gradle_Java_Plugin#Number_of_Forked_JVMs|maxParallelForks]].
 
Within the context of the same test class, all test methods are by default executed '''serially''', by the same thread. However, the order in which the methods are executed does not coincide with the order in which the methods are declared in class.
 
[[#Integration_Tests|Integration tests]], whether executed concurrently or not, share the same context and the context's singletons. Even if the tests are executed serially, a context singleton's state changes introduced by the execution of one of the tests are visible to the subsequent tests, from different classes.
 
=Spring Boot Testing Concepts=
 
{{Internal|Spring_Boot_Testing_Concepts#Overview|Spring Boot Testing Concepts}}

Latest revision as of 05:49, 7 April 2019

External

Internal

Overview

Proper deployment of dependency injection makes both unit and integration testing easier, in that the presence of setter methods and appropriate constructors on classes makes them easier to wire together in a test without having to set up service locator registries or similar structures.

Unit Tests

According to Spring reference documentation, a Spring unit test is a test that does not use a real application context. The unit test should work without such a real application context, and use objects created with the new operator and injected with a direct invocation of the constructor or setter methods. Spring provides MockEnvironment and MockPropertySource that are useful for developing out-of-container tests. Understand this.

Integration Tests

According to Spring reference documentation, a Spring integration test is a test where dependencies are injected with the help of a real application context, albeit a special, test-oriented implementation of it. Yet, these tests do not rely on the presence of an application server, a Spring Boot instance or a deployment environment. These tests are slower to run than unit tests, but much faster than the equivalent Selenium or remote tests, that rely on deployment in an actual deployment environment.

Spring provides an integration testing framework, named Spring TestContext Framework. The framework is annotation-driven, and aims to provide the following:

  • Context management and caching between tests. This generally speeds up successive tests.Clarify if this refers to test functions (@Test) within the same test class, or refers to test functions belonging to different test classes.
  • Dependency injection of test fixture instances.
  • Transaction management. The tests can be coded assuming existence of a transaction.
  • Spring-specific base classes.

Spring TestContext Framework Dependency

dependencies {
    testImplementation('org.springframework:spring-test')
}

This dependency declaration assumes that we are using Gradle Spring dependency-management Plugin and a Maven BOM.

Integration Test Programming Model

Spring TestContext Framework requires a specialized test runner, implemented by the SpringRunner class, which is configured with the @RunWith annotation. The SpringRunner class is an extension of JUnit's BlockJUnit4ClassRunner, which provides functionality of the Spring TestContext Framework to standard JUnit tests. The essential piece of the SpringRunner is a TestContextManager instance, which manages a single TestContext and sends signaling events to all registered TestExecutionListeners.

The test application context is loaded and configured based on the configuration specified with @ContextConfiguration. For configuration details, see:

@ContextConfiguration
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
      AComponent.class,
      BComponent.class})
public class AComponentIntegrationTests {

    @Autowired
    private AComponent aComponent;

    @Test
    public void realContextInjectsComponent() {
        assertNotNull(aComponent);
    }
}
Simplest Spring TestContext Framework Example

Activating Profiles for Integration Tests

Tests are allowed to use a special annotation to declare what profile or profiles are active when the test is executed. That annotation is @ActiveProfiles.

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {...})
@ActiveProfiles("heavy")
public class HeavyIntegrationTest {
  ...
}

The above declaration says that the "heavy" profile will be active while HeavyIntegrationTest is executed.

Playground Example

Playground Activating Profiles for Integration Tests Example

Annotations

Test Logging

When building with Gradle, stdout and stderr of the tests being executed will be shown at the console if the following are configured:

Using Property Configuration Holders with Plain Spring TestContext Framework

Property configuration holders are a Spring Boot pattern. They require Spring Boot dependencies and runtime to function. However, they're useful, so this is a method to get them to work with plain TestContext Framework tests:

Using Property Configuration Holders with Plain Spring TestContext Framework

Concurrent Test Execution

https://www.baeldung.com/spring-5-concurrent-tests

Concurrent test execution is supported by Spring Test Context Framework that comes with Spring 5. JUnit 4 or newer is needed.

When Gradle is used to run the build, the tests are executed by default in a separate (forked) JVM. More than one JVMs dedicated to testing can be configured with maxParallelForks.

Within the context of the same test class, all test methods are by default executed serially, by the same thread. However, the order in which the methods are executed does not coincide with the order in which the methods are declared in class.

Integration tests, whether executed concurrently or not, share the same context and the context's singletons. Even if the tests are executed serially, a context singleton's state changes introduced by the execution of one of the tests are visible to the subsequent tests, from different classes.

Spring Boot Testing Concepts

Spring Boot Testing Concepts