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}.propertiesfiles 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 @ActiveProfilesis 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.
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!