Spring Annotations: Testing

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 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

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 TestContexts 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 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 @Beans.

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 asserts 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 in 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 @ContextConfigurations 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.

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 ran 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 comments
  • blockCommentStartDelimiter: Start delimiter for block comments
  • commentPrefix: The prefix for single-line comments
  • dataSource: Name of the dataSource bean
  • encoding: Specifying the encoding for the scripts
  • errorMode: Which mode to use when an error is encountered
  • separator: The character that's used to separate statements
  • transactionManager: Name of the transaction manager bean
  • transactionMode: 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 will the marked class 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 @JsonComponents.

@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 @PropertySources 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.