Skip to main content

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:

  1. Named Environments: like "dev", "test", "prod"
  2. Feature Flags: like "metrics-enabled", "audit-enabled"
  3. 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:

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

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

java
@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:

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

java
@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:

java
@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:

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

java
@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:

java
@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:

java
@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:

properties
spring.profiles.group.test=test,mock-email,in-memory-db

Then in your test:

java
@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:

java
@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:

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

  1. Keep Profile Names Consistent: Use standardized names across your application.
  2. Don't Overuse Profiles: They should represent significant configuration variations, not minor differences.
  3. Document Your Profiles: Make it clear what each profile does and when to use it.
  4. Test With and Without Profiles: Ensure your application works with default configurations too.
  5. Keep Test Profiles Close to Tests: Put test-specific configurations in test folders rather than main source folders.
  6. Use Profile-Specific Properties Files: Utilize application-{profile}.properties files for cleaner organization.
  7. 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:

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

Exercises

  1. Create a simple Spring Boot application with different database configurations for "dev" and "test" profiles.
  2. Write a test that uses an in-memory database with the "test" profile.
  3. Create a mock service that's only active during testing and implement a test that uses it.
  4. Configure different logging levels for different profiles and verify they work as expected.
  5. 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! :)