Spring Test Profiles
Introduction
When developing applications with Spring Framework, you often need to configure your application differently depending on the environment it's running in - development, testing, staging, or production. Spring profiles provide a way to define different configurations for different environments and activate them conditionally.
In testing scenarios, profiles become particularly important as you might need to:
- Mock external services
- Use in-memory databases instead of production ones
- Configure different property values for testing
- Enable or disable specific features
This guide will walk you through how to use Spring profiles effectively in your tests to create clean, isolated, and environment-specific test configurations.
Understanding Spring Profiles
Spring profiles allow you to define beans and configurations that are only active when specific profiles are enabled. This is done using the @Profile
annotation or XML configuration.
Basic Profile Concepts
Profiles can be:
- Named Environments: like "dev", "test", "prod"
- Feature Flags: like "metrics-enabled", "audit-enabled"
- Test-Specific Configurations: like "mock-repository", "in-memory-db"
Setting Up Test Profiles
Let's look at how to create and use profiles in your Spring tests.
Configuration with Profiles
First, let's create a simple configuration class with profile-specific beans:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class TestDatabaseConfig {
@Bean
@Profile("test")
public DataSource inMemoryDataSource() {
// Create and return an H2 in-memory database for testing
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("!test") // Active when "test" profile is NOT active
public DataSource productionDataSource() {
// Create and return a real database connection
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
}
Activating Profiles in JUnit Tests
Using @ActiveProfiles Annotation
The simplest way to activate profiles in your Spring tests is using the @ActiveProfiles
annotation:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("test")
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Test
void testUserCreation() {
// This test runs with the "test" profile active
// so it will use the inMemoryDataSource bean
User user = new User("testuser", "password");
User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("testuser", savedUser.getUsername());
}
}
Activating Multiple Profiles
You can activate multiple profiles at once:
@SpringBootTest
@ActiveProfiles({"test", "mock-email"})
public class UserRegistrationTest {
// Tests using both "test" and "mock-email" profiles
}
Programmatic Profile Activation
You can also activate profiles programmatically:
import org.springframework.test.context.TestContextManager;
public class ProgrammaticProfileTest {
@BeforeAll
public static void setup() {
System.setProperty("spring.profiles.active", "test,mock-external-api");
}
@AfterAll
public static void cleanup() {
System.clearProperty("spring.profiles.active");
}
// test methods...
}
Common Use Cases for Test Profiles
1. Mock External Services
One common use case is mocking external services in tests:
@Configuration
public class ExternalServiceConfig {
@Bean
@Profile("!test") // For non-test environments
public PaymentGateway realPaymentGateway() {
return new StripePaymentGateway(apiKey, apiSecret);
}
@Bean
@Profile("test") // For test environment
public PaymentGateway mockPaymentGateway() {
return new MockPaymentGateway();
}
}
2. In-Memory Database for Tests
As shown earlier, you can configure an in-memory database for tests:
@SpringBootTest
@ActiveProfiles("test")
public class RepositoryTests {
// Tests will use the in-memory database configured in the "test" profile
}
3. Different Property Values
You can create different properties files for different profiles:
application.properties
(default properties)application-test.properties
(test-specific properties)application-dev.properties
(development-specific properties)
Example application-test.properties
:
# Override default properties for tests
server.port=0
spring.jpa.hibernate.ddl-auto=create-drop
logging.level.org.springframework=ERROR
4. Test-Specific Security Configuration
@Configuration
@Profile("test")
public class TestSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Simplified security for tests
http.csrf().disable()
.authorizeRequests().anyRequest().permitAll();
return http.build();
}
}
Advanced Test Profile Techniques
Conditional Bean Creation
You can use Spring's conditional annotations along with profiles:
@Bean
@Profile("test")
@ConditionalOnProperty(name = "tests.mockMvc.enabled", havingValue = "true")
public MockMvc mockMvc(WebApplicationContext webApplicationContext) {
return MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Profile Expression Logic
Profile expressions support logical operations:
@Profile("test & !integration") // Active in test profile but not when integration profile is active
@Profile("dev | test") // Active when either dev or test profile is active
@Profile("!prod") // Active when prod profile is NOT active
Profile Groups
Spring Boot 2.4+ supports profile groups to activate multiple profiles at once:
In your application.properties
:
spring.profiles.group.test=test,mock-email,in-memory-db
Then in your test:
@ActiveProfiles("test") // This will activate all profiles in the "test" group
Real-World Example: Complete Testing Setup
Let's put everything together in a more complete example. Consider an e-commerce application where you want to test the ordering process without hitting real payment gateways or email services.
First, define your profile-specific configurations:
@Configuration
public class TestConfig {
@Bean
@Profile("test")
public EmailService mockEmailService() {
return new MockEmailService();
}
@Bean
@Profile("test")
public PaymentGateway mockPaymentGateway() {
return new MockPaymentGateway();
}
@Bean
@Profile("test")
public ShippingService mockShippingService() {
return new MockShippingService();
}
}
Then create your test:
@SpringBootTest
@ActiveProfiles("test")
public class OrderProcessingIntegrationTest {
@Autowired
private OrderService orderService;
@Autowired
private MockEmailService emailService; // This is the mock version due to active profile
@Autowired
private MockPaymentGateway paymentGateway; // This is the mock version due to active profile
@Test
void completeOrderProcess() {
// Arrange
Order order = new Order();
order.setItems(Arrays.asList(new OrderItem("Product1", 2), new OrderItem("Product2", 1)));
order.setCustomer(new Customer("John Doe", "[email protected]"));
order.setShippingAddress(new Address("123 Test St", "Test City", "12345"));
order.setPaymentDetails(new PaymentDetails("4111111111111111", "12/25", "123"));
// Act
OrderConfirmation confirmation = orderService.processOrder(order);
// Assert
assertNotNull(confirmation.getOrderId());
assertEquals(OrderStatus.CONFIRMED, confirmation.getStatus());
// Verify mock interactions
verify(paymentGateway).processPayment(any(PaymentDetails.class), anyDouble());
verify(emailService).sendOrderConfirmationEmail(eq("[email protected]"), any(Order.class));
}
}
Best Practices for Test Profiles
- Keep Profile Names Consistent: Use standardized names across your application.
- Don't Overuse Profiles: They should represent significant configuration variations, not minor differences.
- Document Your Profiles: Make it clear what each profile does and when to use it.
- Test With and Without Profiles: Ensure your application works with default configurations too.
- Keep Test Profiles Close to Tests: Put test-specific configurations in test folders rather than main source folders.
- Use Profile-Specific Properties Files: Utilize
application-{profile}.properties
files for cleaner organization. - Clean Up After Tests: If you set profiles programmatically, unset them afterward.
Common Issues and Solutions
Profile Not Being Activated
Problem: Your test is not picking up the profile-specific beans.
Solution:
- Ensure
@ActiveProfiles
is applied to the test class - Check for typos in profile names
- Verify the configuration class is being component-scanned
Multiple Profile Conflicts
Problem: Conflicting beans when multiple profiles are active.
Solution: Make your profile conditions more specific using logical operators:
@Profile("test & !integration")
Profile-Specific Properties Not Loading
Problem: Your application-test.properties
file is not being loaded.
Solution:
- Ensure the file is in the correct location
- Verify the profile name matches exactly
- Check your build configuration to ensure the file is included
Summary
Spring Test Profiles provide a powerful mechanism to configure different environments for testing. They allow you to:
- Isolate tests from production resources
- Mock external dependencies
- Configure different behaviors based on test needs
- Create clean, repeatable test environments
By properly utilizing profiles, you can make your tests more reliable, faster, and independent of external factors. This leads to a more robust testing strategy and ultimately a higher quality application.
Additional Resources
- Spring Framework Documentation on Profiles
- Spring Boot Test Documentation
- Testing Spring Boot Applications
Exercises
- Create a simple Spring Boot application with different database configurations for "dev" and "test" profiles.
- Write a test that uses an in-memory database with the "test" profile.
- Create a mock service that's only active during testing and implement a test that uses it.
- Configure different logging levels for different profiles and verify they work as expected.
- Create a profile group that activates multiple test-related profiles and use it in a test.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)