Profile-Based Testing Architecture
Overviewโ
Profile-based testing provides a robust and scalable solution for managing test configurations in Brobot. This approach eliminates Spring bean conflicts that can occur when multiple test configurations define the same beans with @Primary annotations.
Problem Solvedโ
When running integration tests with Spring Boot, you may encounter errors like:
NoUniqueBeanDefinitionException: No qualifying bean of type 'ScreenCaptureService'
available: more than one 'primary' bean found among candidates
This happens when multiple configurations (test and production) define the same beans as @Primary, causing Spring to be unable to determine which bean to inject.
Solution Architectureโ
1. Profile-Specific Configurationโ
Create isolated test configurations using Spring profiles:
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Profile;
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
@Profile("integration-minimal")
public class IntegrationTestMinimalConfig {
// Test-specific bean definitions
}
2. Test Base Classโ
Provide a common base class for integration tests that extends BrobotTestBase:
import io.github.jspinak.brobot.test.BrobotTestBase;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for integration tests with Spring profiles.
* Extends BrobotTestBase which provides automatic mock mode setup.
*/
public abstract class IntegrationTestBase extends BrobotTestBase {
protected final Logger log = LoggerFactory.getLogger(getClass());
@BeforeEach
@Override
public void setupTest() {
super.setupTest(); // BrobotTestBase handles MockModeManager setup
log.debug("Integration test setup complete");
}
}
Note: BrobotTestBase automatically configures mock mode, headless mode, and fast mock timings. You don't need to call
MockModeManager.setMockMode(true)manually.
3. Profile Propertiesโ
Configure test-specific properties in application-integration.properties:
# Integration Test Configuration
spring.main.allow-bean-definition-overriding=true
spring.main.lazy-initialization=false
# Mock Mode Settings
# Single master switch for mock mode
brobot.mock=true
# Probability of action success (0.0 to 1.0)
brobot.mock.action.success.probability=1.0
# Headless Mode
java.awt.headless=true
# Mock Timing Configuration (ultra-fast for tests)
brobot.mock.time-find-first=0.01
brobot.mock.time-click=0.01
brobot.mock.time-type=0.01
# Logging
logging.level.io.github.jspinak.brobot=DEBUG
Implementation Guideโ
Step 1: Create Minimal Test Configurationโ
Create a configuration class that provides only the essential beans needed for your tests:
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import java.awt.image.BufferedImage;
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.actionOptions.ActionOptions;
import io.github.jspinak.brobot.actions.composites.methods.find.ActionConfig;
import io.github.jspinak.brobot.datatypes.primitives.match.Match;
import io.github.jspinak.brobot.datatypes.primitives.region.Region;
import io.github.jspinak.brobot.datatypes.state.ObjectCollection;
import io.github.jspinak.brobot.reports.ActionResult;
import io.github.jspinak.brobot.services.ScreenCaptureService;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Minimal test configuration with Spring profile isolation.
* Use @TestPropertySource to set brobot.mock=true instead of static blocks.
*/
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
@Profile("integration-minimal")
public class IntegrationTestMinimalConfig {
// Note: Mock mode is configured via @TestPropertySource in test classes,
// not in static blocks. BrobotTestBase handles MockModeManager initialization.
@Bean
@Primary
public ScreenCaptureService screenCaptureService() {
ScreenCaptureService service = mock(ScreenCaptureService.class);
BufferedImage mockImage = new BufferedImage(1920, 1080, BufferedImage.TYPE_INT_RGB);
when(service.captureScreen()).thenReturn(mockImage);
return service;
}
@Bean
@Primary
public Action action() {
// Configure mock Action for tests
Action action = mock(Action.class);
ActionResult successResult = new ActionResult();
successResult.setSuccess(true);
// Add default match for find operations
Match mockMatch = new Match.Builder()
.setRegion(new Region(100, 100, 50, 50))
.setSimScore(0.95)
.build();
successResult.add(mockMatch);
// Configure mock responses
doReturn(successResult).when(action)
.perform(any(ActionOptions.class), any(ObjectCollection[].class));
return action;
}
// Add other required beans...
}
Step 2: Update Test Classesโ
Use the profile-based configuration in your test classes:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.manageStates.StateService;
@SpringBootTest(classes = IntegrationTestMinimalConfig.class)
@ActiveProfiles("integration-minimal")
@TestPropertySource(locations = "classpath:application-integration.properties")
public class MyIntegrationTest extends IntegrationTestBase {
@Autowired
private Action action;
@Autowired
private StateService stateService;
@Test
public void testWorkflow() {
// Your test code here
// Mock mode is automatically enabled by BrobotTestBase
// No bean conflicts!
}
}
Step 3: Handle Component Annotationsโ
For test classes with @Component annotations (like state classes), import them explicitly:
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ActiveProfiles;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import lombok.Getter;
@SpringBootTest(classes = IntegrationTestMinimalConfig.class)
@Import({
MyIntegrationTest.TestState.class,
MyIntegrationTest.AnotherTestState.class
})
@ActiveProfiles("integration-minimal")
public class MyIntegrationTest extends IntegrationTestBase {
@Component
@Getter
public static class TestState {
// Note: @State annotation is for state machine transitions.
// For simple test states, @Component is sufficient.
private final StateImage testElement;
public TestState() {
testElement = new StateImage.Builder()
.setName("TestElement")
.build();
}
}
}
Benefitsโ
1. Isolationโ
- Test configurations are completely isolated from production configurations
- No interference between different test suites
2. Scalabilityโ
- Easy to add new profiles for different test scenarios:
integration-minimal- Minimal beans for fast testsintegration-full- Complete application contextintegration-db- Tests with databaseintegration-ui- Tests with UI components
3. Performanceโ
- Load only required beans, reducing test startup time
- Ultra-fast mock timings for quick test execution
4. Maintainabilityโ
- Clear separation of concerns
- Easy to debug configuration issues
- Explicit declaration of test dependencies
Multiple Profile Strategyโ
You can create different profiles for different testing needs:
Minimal Profile (Fastest)โ
@Profile("integration-minimal")
public class IntegrationTestMinimalConfig {
// Only essential beans
}
Full Profile (Complete Context)โ
@Profile("integration-full")
@Import({BrobotConfig.class, StateManagementConfig.class})
public class IntegrationTestFullConfig {
// Full application context with overrides
}
Database Profileโ
@Profile("integration-db")
@EnableJpaRepositories
public class IntegrationTestDatabaseConfig {
// Database-specific test configuration
}
Troubleshootingโ
Bean Definition Conflictsโ
If you still encounter bean conflicts:
-
Check for component scanning overlap:
@ComponentScan(
basePackages = "io.github.jspinak.brobot",
excludeFilters = {
@Filter(type = FilterType.REGEX, pattern = ".*Test.*"),
@Filter(type = FilterType.REGEX, pattern = ".*Mock.*Config.*")
}
) -
Use @ConditionalOnMissingBean:
@Bean
@ConditionalOnMissingBean(ScreenCaptureService.class)
public ScreenCaptureService screenCaptureService() {
// Bean definition
} -
Enable bean overriding (use with caution):
spring.main.allow-bean-definition-overriding=true
Profile Not Activatedโ
Ensure the profile is activated in your test:
@ActiveProfiles("integration-minimal") // Don't forget this!
Or via environment variable:
SPRING_PROFILES_ACTIVE=integration-minimal ./gradlew test
Mock Mode Not Enabledโ
Ensure mock mode is configured via properties:
// In test class, use @TestPropertySource:
@TestPropertySource(properties = {
"brobot.mock=true",
"java.awt.headless=true"
})
public class MyTest extends BrobotTestBase {
// BrobotTestBase automatically calls MockModeManager.setMockMode(true)
}
Note: Avoid static blocks for mock mode initialization. Use
@TestPropertySourceor extendBrobotTestBasewhich handles initialization properly.
Best Practicesโ
- Keep profiles focused: Each profile should have a single, clear purpose
- Document profile purpose: Add JavaDoc explaining what each profile provides
- Use descriptive names:
integration-minimalis clearer thantest1 - Minimize bean count: Only include beans actually needed for tests
- Reuse common configurations: Create base configurations that profiles can extend
- Test profile combinations: Ensure profiles work together when needed
Migration Guideโ
To migrate existing tests to profile-based configuration:
- Identify conflicting beans in your current test setup
- Create a minimal configuration with only required beans
- Add @Profile annotation to the configuration
- Update test classes to use the new configuration
- Create profile-specific properties file
- Run tests to verify no conflicts
Example: Complete Test Setupโ
Here's a complete example of a test using profile-based configuration:
// === Configuration Class ===
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.manageStates.StateService;
import static org.mockito.Mockito.mock;
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
@Profile("integration-example")
public class ExampleTestConfig {
@Bean
@Primary
public Action action() {
return mock(Action.class);
}
@Bean
public StateService stateService() {
return mock(StateService.class);
}
}
// === Test Class ===
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.datatypes.state.ObjectCollection;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.reports.ActionResult;
import io.github.jspinak.brobot.test.BrobotTestBase;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest(classes = ExampleTestConfig.class)
@ActiveProfiles("integration-example")
@TestPropertySource(properties = {
"brobot.mock=true",
"logging.level.io.github.jspinak.brobot=DEBUG"
})
public class ExampleIntegrationTest extends BrobotTestBase {
@Autowired
private Action action;
@Test
public void testExample() {
PatternFindOptions options = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.BEST)
.build();
StateImage image = new StateImage.Builder()
.setName("TestImage")
.build();
ObjectCollection objects = new ObjectCollection.Builder()
.withImages(image)
.build();
ActionResult result = action.perform(options, objects);
assertTrue(result.isSuccess());
}
}
Conclusionโ
Profile-based testing provides a robust, scalable solution for managing test configurations in Brobot. By isolating test configurations with Spring profiles, you can:
- Eliminate bean conflicts
- Improve test performance
- Maintain cleaner test code
- Scale your test suite effectively
This approach is particularly valuable for large projects with complex Spring configurations and multiple test scenarios.
Related Documentationโ
Testing Guidesโ
- Testing Introduction - Overview of Brobot testing approaches
- Unit Testing - Unit testing patterns with BrobotTestBase
- Integration Testing - Integration test patterns with Spring
- Test Utilities - BrobotTestBase and testing utilities reference
- Mock Mode Guide - Comprehensive guide to mock mode
- Mock Mode Manager - Centralized mock mode management
- Testing Strategy - Overall testing strategy and patterns
Mock Mode Documentationโ
- Mock Stochasticity - Probabilistic mock behavior
- Mock Mode Migration - Migrating to MockModeManager
- ActionHistory Mock Snapshots - Creating mock data
- Action Recording - Recording actions for mocks
- ActionHistory Integration Testing - Testing with ActionHistory
Configuration Documentationโ
- BrobotProperties Usage - Complete configuration guide
- Properties Reference - All available properties
- Auto-Configuration - Spring Boot auto-configuration
- Headless Configuration - Headless mode setup
ActionConfig Documentationโ
- ActionConfig Overview - Introduction to ActionConfig
- ActionConfig Examples - Practical examples
- ActionConfig Reference - Complete API reference
- Action Chaining - Chaining actions together
- Convenience Methods - Simplified action methods
State Managementโ
- States Overview - Introduction to states
- Transitions - State transitions
- Annotations Guide - @State and @Transition usage
Advanced Topicsโ
- Enhanced Mocking - Advanced mock scenarios
- CI/CD Testing - Mock mode in CI/CD pipelines
- Debugging Pattern Matching - Troubleshooting pattern matching
Getting Startedโ
- Quick Start - Getting started with Brobot
- AI Brobot Project Creation - Complete API reference and patterns