Unit testing vs integration testing
As Spring beans are nothing but POJOs, we can always write unit tests for individual beans that are simply instantiated using the new operator, without Spring or any other container. But that's not good enough if we also would like to test whether beans are wired together properly or not and whether everything works as expected when tested in a Spring container; for those scenarios, we need integration testing.
Spring TestContext
The Spring TestContext Framework is a foundational component of the Spring Framework’s spring-test module and serves as the underlying infrastructure for Spring-based testing. It provides a generic, annotation-driven programming model for both unit and integration tests. The framework manages the loading and caching of the Spring ApplicationContext, injects dependencies into test instances, and provides built-in support for transactional test execution. These capabilities are offered in a testing-framework-agnostic manner, allowing integration with frameworks such as JUnit or TestNG.
Spring integration test with JUnit 5 (JUnit Jupiter)
The Spring TestContext Framework is activated through the use of test configuration annotations such as @ExtendWith and @ContextConfiguration. The JUnit's @ExtendWith annotation (tutorials here) integrates the Spring testing support with JUnit Jupiter, while @ContextConfiguration defines how the Spring ApplicationContext should be loaded for the test. Together, these annotations bootstrap the TestContext infrastructure and enable dependency injection, context management, and other testing features.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
class MyServiceTest {
@Autowired
private MyService myService;
}
The @Autowired annotation is available in this test because the TestContext Framework performs dependency injection on the test instance after the ApplicationContext has been created. Once the context is loaded, Spring manages the test class in a similar manner to a regular Spring-managed bean, allowing dependencies to be injected directly into fields, constructors, or setter methods.
Example
Creating a simple Spring test application.
pom.xml<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>7.0.3</version>
</dependency>
ShoppingCart
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCart {
@Autowired
private OrderService orderService;
private List<Order> orders = new ArrayList<>();
public void addItem(String name, int qty) {
orders.add(new Order(name, qty));
}
public String checkout() {
String msg = orderService.placeOrders(orders);
orders.clear();
return msg;
}
public OrderService getOrderService() {
return orderService;
}
}
OrderService
package com.logicbig.example;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class OrderService {
public String placeOrders(List<Order> orders) {
//simulating order placement
return orders.size() + " orders placed";
}
}
package com.logicbig.example;
public class Order {
private String item;
private int qty;
public Order(String item, int qty) {
this.item = item;
this.qty = qty;
}
public String getItem() {
return item;
}
public int getQty() {
return qty;
}
}
AppConfig
package com.logicbig.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.logicbig.example")
public class AppConfig {
}
JUnit 5 test
In JUnit 5 we use @ExtendWith (tutorial here) rather than @RunWith The difference between the two is, @RunWith (JUnit 4) replaces the default test runner with a custom test runner. @ExtendWith (JUnit 5) is more flexible and plugs into the extension points.
Spring provides SpringExtension to be used with @ExtendWith.
SpringExtension is actually the JUnit 5 successor to SpringRunner, both are essentially the same thing, i.e. both provides Spring context support in JUnit tests. Alternatively, you can use @SpringJUnitConfig which combines @ExtendWith(SpringExtension.class) and @ContextConfiguration.
package com.logicbig.example;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class ShoppingCartTest {
@Autowired
private ShoppingCart shoppingCart;
@Test
public void testDiCorrectness(){
//Testing dependency injection correctness
assertNotNull(shoppingCart);
assertNotNull(shoppingCart.getOrderService());
}
@Test
public void testCheckout() {
shoppingCart.addItem("Item1", 2);
shoppingCart.addItem("item2", 5);
String result = shoppingCart.checkout();
Assertions.assertEquals("2 orders placed", result);
}
}
Output$ mvn test [INFO] Scanning for projects... [INFO] [INFO] ------------< com.logicbig.example:spring-testing-junit-5 >------------- [INFO] Building spring-testing-junit-5 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-testing-junit-5 --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\spring-testing-junit-5\src\main\resources [INFO] [INFO] --- compiler:3.14.1:compile (default-compile) @ spring-testing-junit-5 --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 4 source files with javac [debug target 25] to target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-testing-junit-5 --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\spring-testing-junit-5\src\test\resources [INFO] [INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ spring-testing-junit-5 --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 1 source file with javac [debug target 25] to target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-testing-junit-5 --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.ShoppingCartTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.242 s -- in com.logicbig.example.ShoppingCartTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.110 s [INFO] Finished at: 2026-01-30T15:47:07+08:00 [INFO] ------------------------------------------------------------------------
Example ProjectDependencies and Technologies Used: - spring-context 7.0.3 (Spring Context)
Version Compatibility: 5.0.0.RELEASE - 7.0.3 Version compatibilities of spring-context with this example: Versions in green have been tested.
- spring-test 7.0.3 (Spring TestContext Framework)
- junit-jupiter-engine 6.0.2 (Module "junit-jupiter-engine" of JUnit)
- JDK 25
- Maven 3.9.11
|