Introduction
The Spring Framework is a very robust framework, released in 2002. Its core features can be applied to plain Java applications or extended to complex, modern web applications.
As it's constantly being updated and is following new architectural and programming paradigms, it offers support for many other frameworks that work hand-in-hand with it.
With such a vast array of functionalities, it's only normal that it introduces us to some new annotations, which are a key part of developing Spring applications.
Spring's configuration is fully customizable, which was originally done through XML configuration files. However, this approach has become outdated, and most people nowadays resort to annotation configuration.
That being said, this series of articles aims to unravel the options you as a developer have to configure and use the Spring framework:
- Spring Annotations: @RequestMapping and its Variants
- Spring Annotations: Core Framework Annotations
- Spring Annotations: Spring Cloud
- Spring Annotations: Testing Annotations
Spring Testing Annotations
Test-Driven Development (TDD) has become an important topic nowadays and it's considered extremely bad practice not to properly test your applications.
There are several widely-used frameworks that make this job a lot easier for developers, where JUnit is the most commonly used one.
To catch up with modern programming practices, Spring has launched a new starter
dependency, spring-boot-starter-test
, which comprises of a few frameworks:
- JUnit
- Spring Test & Spring Boot Test
- AssertJ
- Hamcrest
- Mockito
- JSONassert
- JsonPath
In this article, we'll cover the following test annotations:
- @BootstrapWith
- @ContextConfiguration
- @WebAppConfiguration
- @ContextHierarchy
- @ActiveProfiles
- @Rollback
- @Commit
- @BeforeTransaction
- @AfterTransaction
- @Sql
- @SqlGroup
- @SqlConfig
- @SpringBootTest
- @DataJpaTest
- @DataMongoTest
- @WebMvcTest
- @MockBean
- @AutoConfigureMockMvc
- @JsonTest
- @TestPropertySource
- @Timed
- @Repeat
@BootstrapWith
The @BootstrapWith
annotation is an annotation that you'll likely very rarely use. The default configurations for the Spring TestContext Framework are more than good enough for most use cases.
If it isn't, you can change the ContextLoader
or implement custom TestContext
s amongst a myriad of other configurations you can change.
Again, this is an annotation you likely won't be using if you're not a part of a team that really needs custom configuration for the Spring TestContext Framework.
@ContextConfiguration
The @ContextConfiguration
is an integration test annotation applied at a class-level used to define how Spring should load the ApplicationContext
.
This annotation can be applied alongside the @Component
(as well as annotations such as @Service
, @Repository
, etc.) and @Configuration
annotations as well as any class that contains @Bean
s.
You can use the annotation to refer either to XML files or Java classes:
@ContextConfiguration("/some-test-configuration-file.xml")
// @ContetConfiguration(locations = "/some-test-configuration-file.xml")
// You can use the optional `locations` flag as well.
public class ApplicationTests {
// Testing code...
}
@ContextConfiguration(classes = TestConfiguration.class)
public class ApplicationTests {
// Testing code...
}
For an example, let's say we have TestBean
:
@Configuration
public class TestBean {
@Bean
public DeveloperService developerService() {
return new DeveloperService();
}
}
If we wanted to do some assert
s on this bean, we'd do something along the lines of:
@ContextConfiguration(classes = TestBean.class)
public class ApplicationTests {
@Autowired
private DeveloperService;
@Test
public void testBean() {
Developer dev = developerService.getDeveloperById(5);
assertEquals("David", dev.getName());
}
}
Nowadays, it's preferable to rely on the class approach, as XML is generally considered an outdated approach for registering beans. If you have more than one class, of course, you'd simply specify them via classes = {TestBean.class, TestBean2.class, TestBean3.class}
, etc.
This brings us to the @Test
annotation, which will be covered in detail below. For now, let's simply use it for illustration purposes.
@WebAppConfiguration
If you'd like to ensure that Spring loads a WebApplicationContext
for your tests instead of the regular ApplicationContext
, you can use the @WebAppConfiguration
annotation alongside the @ContextConfiguration
annotation:
@ContextConfiguration(classes = TestBean.class)
@WebAppConfiguration
public class ApplicationTests {
@Autowired
private DeveloperService;
// Rest of the code...
}
Alternatively, you can specify the value
flag, or rather, the location of the WebApplicationContext
, if it's not located in the default src/main/webapp
directory:
@WebAppConfiguration("some/other/location")
public class ApplicationTests {}
@ContextHierarchy
Another annotation that is generally rarely used (I haven't personally seen anyone use it in a project) is the @ContextHierarchy
annotation.
It allows the developer to define multiple @ContextConfiguration
s in levels via a parent-child relationship.
The idea is that the child contexts can use the beans registered in the parent contexts and this improves the reusability of beans:
@ContextHierarchy({
@ContextConfiguration(classes = ApplicationTestConfiguration.class),
@ContextConfiguration(classes = WebApplicationTestConfiguration.class)
})
public class ApplicationTests {
}
If you'd like to read more about this annotation, the documentation contains some in-depth information about context hierarchy.
@ActiveProfiles
The @ActiveProfiles
annotation is a pretty straightforward and simple annotation. It defines which profile should be active when loading the context configuration:
@ContextConfiguration
@ActiveProfiles("dev")
public class ApplicationTests {}
This indicates that the "dev" profile should be active.
The name of the annotation implies that we can define multiple profiles, which we can:
@ContextConfiguration
@ActiveProfiles({"dev", "prod"})
public class ApplicationTests {}
If you'd like to read more about Spring Profiles, we've got you covered!
@Rollback
Sometimes, when dealing with databases, we want to roll-back the changes we've made, especially if we've caused an exception.
The @Rollback
annotation defines whether the transaction of a method marked with @Transactional
should be rolled back, after the test method calling it has completed.
It can be applied to class and method level:
- Class Level: Defines default rollback for all test methods within the class
- Method Level: Defines the rollback for the specific test method
@Rollback(true)
@Test
public void someTest() {
// ...calling some transactional method
}
After the test is done, all changes made by the transactional method will be rolled back.
An interesting point to make is the fact that you can set the optional flag to false
, in which Spring ensures that the changes are not rolled back. Setting the @Rollback
annotation to false
will behave exactly the same as @Commit
.
@Commit
Appending to the previous section, the @Commit
annotation is used when we want to assure the changes in the database after running the test methods.
It behaves the same as @Rollback(false)
and can be applied to class or method level:
@Commit
@Test
public void someTest() {
// ...calling some transactional method
}
@BeforeTransaction
Sometimes, we want to run specific code pieces before transactions are made. To do so, we obviously need to define methods specifically written for this.
To invoke them before each transaction, we simply annotate them with the @BeforeTransaction
annotation:
@BeforeTransaction
void methodBeforeTransaction() {
// ...ran before a transaction
}
For the annotation to work properly, you need to mark your transactional methods with @Transactional
.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Note: As of Spring 4.3, these methods are not required to be public.
@AfterTransaction
With the same nature as the @BeforeTransaction
annotation, the @AfterTransaction
annotation runs a certain method after a transaction has been made:
@AfterTransaction
void methodAfterTransaction() {
// ...ran after a transaction
}
Note: As of Spring 4.3, these methods are not required to be public.
@Sql
Using the @Sql
annotation, and passing in the name(s) of the schemas we wish to be executed, we can programmatically (or declaratively) execute SQL scripts.
By default, these scripts are run before any @Before
methods.
If we define a script, such as createTable.sql
:
CREATE TABLE ITEM (ITEM_ID INT PRIMARY KEY, ITEM_NAME VARCHAR(256) NOT NULL);
We can reference it and execute it easily:
@Test
@Sql("/createTable.sql")
public void itemTest {
// ...some code that depends on the sql script above
}
@SqlGroup
The @SqlGroup
annotation allows us to bundle together multiple SQL scripts and run them.
If we have another script, such as one for dropping the same table, dropTable.sql
:
DROP TABLE ITEM;
We can bundle together the createTable.sql
script with the dropTable.sql
script to run before and after the test method, for an example:
@Test
@SqlGroup({
@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, scripts = ""),
@Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD, scripts = ""),
})
public void itemTest {
// ...item table gets created, tested by the code and then dropped
}
@SqlConfig
As the name implies, following the standard Spring annotation examples, the @SqlConfig
annotation is used to define the configuration of SQL scripts - how they're parsed and executed.
It can be applied to class-level or method-level. Integration tests, which require global configuration for all running SQL scripts, typically use the class-level approach whereas the method-level approach is for local configurations of certain methods:
@Test
@Sql(scripts = "/createTable.sql",
config = @SqlConfig(attribute = "val", attribute2 = "val"))
public void itemTest {
// Some code...
}
There are 9 attributes you can pass onto the @SqlConfig
annotation:
blockCommentEndDelimiter
: End delimiter for block commentsblockCommentStartDelimiter
: Start delimiter for block commentscommentPrefix
: The prefix for single-line commentsdataSource
: Name of thedataSource
beanencoding
: Specifying the encoding for the scriptserrorMode
: Which mode to use when an error is encounteredseparator
: The character that's used to separate statementstransactionManager
: Name of the transaction manager beantransactionMode
: Which mode to use when executing SQL scripts
@SpringBootTest
The @SpringBootTest
annotation searches for the test class annotated with @SpringBootConfiguration
which in most cases is our main application class as @SpringBootApplication
includes the previous annotation within itself.
Once found, it constructs the application context for the test environment. You can even start a web environment using the webEnvironment
attribute:
@SpringBootTest
public class IntegrationTests {
// Rest of the code
}
@SpringBootTest(webEnvironment = pringBootTest.WebEnvironment.RANDOM_PORT)
public class WebEnvIntegrationTests {
// Rest of the code
}
@DataJpaTest
Using the @DataJpaTest
annotation, we can test JPA applications. It's applied on class-level and constructs an application context for all the @Enitity
classes, alongside an embedded database which is applied by default.
Note: Regular @Component
classes are not loaded in the application context created by the @DataJpaTest
annotation.
It's used alongside the @RunWith(SpringRunner.class)
annotation, which indicates which facilities the marked class will use.
By default, all of the JPA transactions will roll back (you can change this behavior by applying either @Rollback(false)
or @Commit
):
@RunWith(SpringRunner.class)
@DataJpaTest
public class SomeJpaTest {
// Rest of the code
}
This is a classic JPA test, though, if you'd like to use the real database, instead of the embedded in-memory database provided, you can simply add another annotation to prevent such behavior:
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class SomeJpaTest {
// Rest of the code
}
@DataMongoTest
Very similar to the @DataJpaTest
annotation, to perform classic MongoDB tests, we apply the @DataMongoTest
annotation alongside the @RunWith(SpringRunner.class)
annotation.
Keep in mind that this annotation is used when the test it's applied to only tests MongoDB components and adds only @Document
classes to the application context:
@RunWith(SpringRunner.class)
@DataMongoTest
public class SomeMongoTest {
// Rest of the code
}
Then again, if you'd like to run this with the real database, and not the in-memory embedded database provided by Mongo, you can exclude this option:
@RunWith(SpringRunner.class)
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class SomeMongoTest {
// Rest of the code
}
@WebMvcTest
Again, very similar to the @DataJpaTest
and the @DataMongoTest
annotations, to perform classic Spring MVC tests, we apply the @WebMvcTest
annotation alongside the @RunWith(SpringRunner.class)
annotation.
Keep in mind that the effects of this annotation only apply to the MVC infrastructure. That being said, it doesn't instantiate the whole context.
The annotation can be used to test a single controller, by passing it as an attribute such as @WebMvcTest(SomeController.class)
.
To instantiate other needed dependencies, such as services, we typically use the @MockBean
annotation. @WebMvcTest
configures MockMvc
which can be used to easily and quickly test MVC controllers and instantiate other collaborators:
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class ControllerTests {
// Auto-configured to make mocking easier
@Autowired
private MockMvc mockMvc;
@MockBean
private SomeBean someBean;
@Test
public void someTest() {
// Test logic
}
}
@MockBean
When testing specific units, such as, say, a controller, we want to isolate them as much as we can. Since most Spring Application components rely on a bunch of other components (dependencies), it's quintessential to make sure these components are all individually testable.
To successfully isolate the objects we want to test, while still allowing the application to work fine, we mock or simulate the dependencies. A @MockBean
annotation is used when we want to mock a dependency in an application:
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class ControllerTests {
// Auto-configured to make mocking easier
@Autowired
private MockMvc mockMvc;
@MockBean
private SomeBean someBean;
@Test
public void someTest() {
// Test logic
}
}
In this example, the someBean
dependency is simulating an actual dependency. If the bean exists in the context, the mock replaces it. If it doesn't exist, the mock is added to the context as a bean.
Note: There's a difference between the @Mock
and @MockBean
annotations. The @Mock
annotation comes from the Mockito library, and is equivalent to calling the Mockito.mock()
method. On the other hand, @MockBean
is the Spring library wrapper of the @Mock
annotation.
@AutoConfigureMockMvc
As the name suggests, the @AutoConfigureMockMvc
annotation, when applied to a test class, will auto-configure MockMvc
, the same way @WebMvcTest
auto-configures it.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ControllerTests {
@Autowired
private MockMvc mockMvc;
// Rest of the logic
}
If you want to focus only on the web layer, consider using the @WebMvcTest
annotation instead.
@JsonTest
A lot of applications are dealing with JSON serialization/deserialization. Therefore it makes a lot of sense to make sure it's working properly while testing the application. By using the @JsonTest
annotation, Spring auto-configures the supported JSON mapper (Jackson, Gson or JSONB).
It's typically used alongside the @RunWith(SpringRunner.class)
and is used for classic JSON tests, scanning for @JsonComponent
s.
@RunWith(SpringRunner.class)
@JsonTest
public class JsonTests {
@Test
public void someJsonTest() {
// Rest of the logic
}
}
@TestPropertySource
The @TestPropertySource
annotation is applied to class-level, and defines the locations to the property sources that we want to use for the test.
These properties are saved as a set of @PropertySource
s in the application context's environment. These properties have a priority over the system or application properties.
Essentially, when we wish to override the system/application properties with specific properties for our tests, we simply annotate the test class:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
@TestPropertySource("classpath:applicationtest.properties")
public class ApplicationTest {
// Rest of the logic
}
On the other hand, you can specify inline properties, instead of the whole properties file:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
@TestPropertySource(properties = {"sa.website_name = stackabuse", "sa.website_url = www.stackabuse.com"})
public class ApplicationTest {
// Rest of the logic
}
@Timed
The @Timed
annotation defines the time in milliseconds in which the test method has to finish execution, otherwise it'll fail:
@Timed(millis = 1000)
public void testMethod() {
// Some test logic
}
If the test takes more than a second to execute, it'll fail. This includes all repetitions of the method, if the @Repeat
annotation is present.
@Repeat
The @Repeat
annotation defines how many times a test method should be repeated:
@Repeat(5)
@Test
public void testMethod() {
// Some test logic
}
This test will be repeated five times.
Conclusion
The Spring framework is a powerful and robust framework which really changed the game when it comes to developing web-applications. Amongst all of the things it supports, it offers great TDD support for Spring Applications and allows developers to easily and quickly set up any kind of tests.