I want a Spring Boot property to have an impossible to guess random value by default (for security reasons), so I am trying to use a random UUID as the default value, using code like this:
#Service
public class UserServiceImpl implements UserService {
...
#Autowired
public UserServiceImpl(#NonNull final PasswordEncoder passwordEncoder,
#NonNull final UserRepository userRepository,
#NonNull #Value("${administrator.password:${random.uuid}}") final String administratorPassword) {
...
}
But my Cucumber Spring Boot tests are complaining about the ${random.uuid} thus:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userServiceImpl' defined in file [.../UserServiceImpl.class]: Unexpected exception during bean creation; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'random.uuid' in value "administrator.password:${random.uuid}"
What do I have to do to get my application to use a random property value?
The problem might be related to test slicing. If I run a clean Spring boot project's test with:
#SpringBootTest
class DemoApplicationTests {
#Value("${nonexistingValue:${random.uuid}}")
private String someVal;
#Test
public void someTest() {
assertThat(someVal).contains("-");
}
}
The test passes. However, if I change #SpringBootTest to #ExtendWith({SpringExtension.class}) or #RunWith(SpringRunner.class), the test fails. ${random.uuid} and similar expressions should be available in a normal runtime environment.
Because of this slicing, it seems that the RandomValuePropertySource is not available. A rather inelegant work-around is to explicitly add it to the context, using a PropertySourcesPlaceholderConfigurer bean created in your test context:
#Configuration
public class CucumberBeansConfiguration {
#Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final var configurer = new PropertySourcesPlaceholderConfigurer();
final var sources = new MutablePropertySources();
sources.addFirst(new RandomValuePropertySource());
configurer.setPropertySources(sources);
return configurer;
}
}
${random.uuid} can only be used if RandomValuePropertySource is available.
As workaround, you can define a "dummy default value" and detect this in your code:
#Autowired
public UserServiceImpl(#NonNull final PasswordEncoder passwordEncoder,
#NonNull final UserRepository userRepository,
#NonNull #Value("${administrator.password:no-default-vale-provided}") final String administratorPassword) {
this.adminPassword = "no-default-value-provided".equals(administratorPassword)
? UUID.randomUUID().toString()
: administratorPassword;
}
If you have [`RandomValuePropertySource`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/RandomValuePropertySource.html) available, then the SpEL expression `${random.uuid}` should indeed resolve to a random UUID. `${…}` is a property placeholder and `#{$…}` is SpEL syntax.
You need to change your annotation:
#NonNull #Value("#{${administrator.password} ?: ${random.uuid}}") final String administratorPassword
Cucumber uses Springs TestContextManager. By annotating your context configuration with #CucumberContextConfiguration Cucumber knows which class use to start the test context.
import com.example.app;
import org.springframework.boot.test.context.SpringBootTest;
import io.cucumber.spring.CucumberContextConfiguration;
#CucumberContextConfiguration
#SpringBootTest
public class CucumberSpringConfiguration {
}
Do make sure the CucumberSpringConfiguration is on the cucumber.glue path.
Related
I have configuration file:
#Configuration
public class UrlAnalyzerConfiguration {
#Bean
#Qualifier("usersDatabase")
public File getUsersDatabaseFile() {
return Paths.get("src/main/resources/database/users.csv").toFile();
}
#Bean
#Qualifier("urlsDatabase")
public File getUrlsDatabaseFile() {
return Paths.get("src/main/resources/database/urls.csv").toFile();
}
}
And configuration for tests:
#TestConfiguration
public class TestConfig {
#Bean
#Qualifier("usersDatabase")
public File getUsersDatabaseTestFile() {
return Paths.get("src/test/resources/database/users.csv").toFile();
}
#Bean
#Qualifier("urlsDatabase")
public File getUrlsDatabaseTestFile() {
return Paths.get("src/test/resources/database/urls.csv").toFile();
}
}
My repository uses one of that beans:
private final File usersDatabase;
#Autowired
public UserRepositoryImpl(#Qualifier("usersDatabase") File usersDatabase) {
this.usersDatabase = usersDatabase;
}
My repostitory tests file:
#SpringBootTest
#Import(TestConfig.class)
public class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
// tests here
}
At the result my main program works properly, but tests fail with followi message in the end:
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'java.io.File' available: expected single matching bean but found 2: getUsersDatabaseTestFile,getUsersDatabaseFile
I have found the problem:
#TestConfiguration class is used in addition to your application’s
primary configuration.
It means, that tests use #Configuration and #TestConfiguration together! But anyway, how can I use then my beans properly ?
You can use a combination of #Profile and #ActiveProfiles to load only the desired classes for your tests. See prototype below.
Configuration
#Configuration
#Profile("test")
public class TestOnlyConfigs {
// define beans
}
Test
#ActiveProfiles("test")
public class Tests {
// write tests
}
References
#Profile
#ActiveProfiles
I'm writing a junit test where I need to Autowire a specific implementation of an interface. I'm using the #Mock annotation to Autowire the implementation.
I'm using profiles and a Configuration file to determine which implementation to Autowire.
When running the test class EmailTest , the following error message appears on the console:
Caused by: java.lang.IllegalStateException: Unable to register mock bean .... expected a single matching bean to replace but found [customerEmailSender, emailSenderImpl_1, emailSenderImpl_2]
The reason is that the Spring doesn't find or use the config class : BeanConfiguration.
I know this because I put a breakpoint in the class BeanConfiguration, and the application doesn't break.
What could be the raeson that Spring doesn't find or use the configuration class BeanConfiguration.
#RunWith(SpringRunner.class)
#ActiveProfiles(profiles = {"test-unit"})
#Import(BeanConfiguration.class)
public class EmailTest {
#MockBean
private CustomerEmailSender customerEmailSender;
}
#Configuration
public class BeanConfiguration {
#Profile({"test-unit"})
#Bean(name = "customerEmailSender")
public CustomerEmailSender emailSenderImpl_1(){
return new EmailSenderImpl_1();
}
#Profile({"prd"})
#Bean(name = "customerEmailSender")
public CustomerEmailSender emailSenderImpl_2(){
return new EmailSenderImpl_2();
}
}
Check if BeanConfiguration package is till in spring's component scan boundary
The problem is that #MockBean does not know which bean to replace (and you have multiple beans of the same interface).
Try
#MockBean(name = "customerEmailSender")
private CustomerEmailSender customerEmailSender;
I created a new test in my Project. For this one I used #ContextConfiguration with an internal Configuration class in the same class as the Test. But now my other tests are failing because they are using the configuration of the new test.
How is this possible, I thought it is not possible to use a configuration inside of a test class from outside.
When I remove the internal configuration from the new test every other test works fine again.
#DataJpaTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextConfiguration(classes = EventServiceTest.Config.class)
class EventServiceTest {
#Configuration
#Import({WorkingTimeConfig.class,
PartnerConfig.class,
ProjectConfig.class,
UserConfig.class,
AccountGroupConfig.class,
LanguageConfig.class,
CountryConfig.class,
EventConfig.class,
LanguageConfig.class})
static class Config {
#SuppressWarnings("unused")
#MockBean(reset = MockReset.BEFORE)
private UserAttendanceBoard userAttendanceBoard;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public ImpersonateProperties impersonateProperties() {
return new ImpersonateProperties();
}
}
...
}
Now this Test is not working:
#Import(MailSenderAutoConfiguration.class)
#DataJpaTest
#Transactional
public class ServiceTimeEntryServiceTest {
private ServiceTimeService serviceTimeService;
private ServiceTimeEntryRepository repository;
#Autowired
public ServiceTimeEntryServiceTest(ServiceTimeService serviceTimeService, ServiceTimeEntryRepository repository) {
this.serviceTimeService = serviceTimeService;
this.repository = repository;
}
#Test
void getAllByAccountId() {...}
This error is thrown if I try to start my old tests:
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'passwordEncoder' defined in class path resource [de/hlservices/timetracking/api/business/event/EventServiceTest$Config.class]: Cannot register bean definition
Thanks for your help :)
As Maciej Kowalski pointed out, this issue is probably related to a #ComponentScan annotation.
If you're using it, consider adding an excludeFilter to ensure you only get what you really want. You might want to exclude other configuration classes to be found by your #ComponentScan annotation:
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.ANNOTATION,
value = Configuration.class)
})
Btw: I really recommend using IntelliJ IDEA as IDE because of the awesome spring support.
You can lookup what beans/components are found by your scan just by clicking on the green icon left of your code (line:9) :
This makes debugging scanning issues way easier.
I had the same issue in my project and it was due to the fact that the #ComponentScan was picking up also that class due to the #Configuration annotation.
Everything worked fine when I removed that annotation and thus making the component scan to omit it. So you can have just like that:
#Import({WorkingTimeConfig.class,
PartnerConfig.class,
ProjectConfig.class,
UserConfig.class,
AccountGroupConfig.class,
LanguageConfig.class,
CountryConfig.class,
EventConfig.class,
LanguageConfig.class})
static class Config {
Removing #Configuration annotation did not prevent #ContextConfiguration(classes = EventServiceTest.Config.class) config to pick it up anyway.
I am trying to add spring-context-indexer into the libraries of my project. However, when running an integration test I would like to not use a Bean from a dependency (SystemConfiguration), but override it using #Primary to return a new MockSystemConfiguration(). That doesn't seem to work with the spring-context-indexer.
Here is my test
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = CustomTestSpringConfig.class)
class SpringDataConfigurationTest {
#Test
void applicationContextCanStart() {
assertTrue(true);
}
}
And CustomTestSpringconfig
#Configuration
#ComponentScan(basePackageClasses = SystemConfiguration.class)
public class CustomTestSpringConfig {
#Primary
#Bean(name = "SystemConfiguration")
public SystemConfiguration systemConfiguration() {
return new MockSystemConfiguration();
}
}
The real SystemConfiguration is defined in a different jar that already has the spring-component-indexer in it.
#Component("SystemConfiguration")
public class SystemConfigurationImpl implements SystemConfiguration {
What is happening is the real SystemConfiguration Bean is being used instead of the one annotated with #Primary.
If it's your project's dependency (and you import this dependency into your app but you have a control over the dependency) it makes sense to declare a bean with conditions. F.e.:
#Bean
#ConditionalOnMissingBean(SystemConfiguration.class) // mock bean in tests would be of the the same type so this one is skipped
SystemConfiguration systemConfiguration() {
return new SystemConfigurationImpl();
}
If you don't have a control over the dependency and you need the test bean it's natural to run tests with test profile, so you can play with #Profile("test") and #ActiveProfiles("test").
Good examples here: Spring Profiles
I am trying to get Cucumber working with Spring. In our code, we are already using java based Spring configuration. I am having trouble getting it to work in the following scenario. Can someone please help?
Today , in our integration test classes we use #ContextConfiguration for each class and provide the config class that is declared with in that integration test class for loading the beans. Config class is annotated with #Configuration. Same bean could be instantiated differently in 2 different classes Config classes used in 2 different integration test classes.
So when I use Cucumber, since the Contextconfiguration differs on different classes, it looks for 'Cucumber.xml' . In the xml file, I am using component-scan to scan the cucumber step definition classes by giving the package name that these classes use (both classes have same package name) . Since all beans gets loaded in same context, Cucumber is failing to load the beans when it finds the same bean defined in these different config classes .
How do I get over this problem of creating same bean but in different ways and use them in different classes?
Please note that I am not looking for a solution that creates lot of churn from our existing coding practices, so having per-test-xml file is not an option for me.
Here is how our code looks:
Class NameAndAddressProviderIntegrationTestSteps :-
#ContextConfiguration(locations="classpath:cucumber.xml")
public class NameAndAddressProviderIntegrationTestSteps {
#Configuration
#Import({
xyz.class,
abc.class,
NameAndAddressProvider.class
})
#ImportResource({
"file:configuration/spring-configuration/abc.xml",
"file:configuration/spring-configuration/xyz.xml"
})
public static class Config {
#Bean
AccountHolderDataMap dataMap() {
AccountHolderDataMap data = new AccountHolderDataMap();
data.put(ID,
new AccountHolderData(customerID));
data.get(customerID).setCustomerplaceID(testCustomerplaceID);
return data;
}
}
#Inject
private NameAndAddressProvider provider;
#When("^I call nameandAddress provider with a 'customerId'$")
public void i_call_nameandAddress_provider_with_a_customerId() throws DependencyException {
System.out.println("Entering when method");
names = provider.getNames(customerID);
System.out.println(provider.toString());
}
......
}
Class AddressProviderIntegrationTestSteps:-
#ContextConfiguration(locations="classpath:cucumber.xml")
public class AddressProviderIntegrationTestSteps {
#Configuration
#Import({
abc.class,
xyz.class,
AddressesProvider.class
})
#ImportResource({
"file:configuration/spring-configuration/test-environment.xml",
"file:configuration/spring-configuration/test-logging-config.xml"
})
public static class Config {
#Bean
#DependsOn("Environment")
AccountHolderDataMap data() {
AccountHolderDataMap data = new AccountHolderDataMap();
data.put(testCustomerID,
new AccountHolderData(testCustomerID, testCustomerplaceID,businessType));
return data;
}
}
private static final String testCustomerID = "1234";
private static final String testMarketplaceID = "abc";
#Inject
private AddressesProvider provider;
#When("^I call AddressesProvider provider with a 'CustomerID'$")
public void i_call_AddressesProvider_provider_with_a_CustomerID() throws Throwable {
List<Address> addresses = provider.getAddresses(testCustomerID);
Log.info(addresses.get(0).toString());
assertTrue(addresses.size()==1);
}
}
And here is the nested exception I am getting:-
"nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [....AccountHolderDataMap] is defined: expected single matching bean but found 2: dataMap,data"
Appreciate your help!
I've managed multiple sources for bean-definitions. You can use this at a starting point (or others in the internet as your question is quite old)
I am using spring4, see my other cucumer post for the pom
At the stepdefs use a config.class
#ContextConfiguration(classes = { CucumberConfiguration.class })
public class StepdefsTest123 {
#Autowired bean; // from cucumberBeanContext.xml
#When("^A$")
public void a() throws Throwable {
System.out.println(bean.getFoo());
}
}
in the config-class add aditional beandefinitions
#Configuration
#ComponentScan(basePackages = "package.here.cucumber")
#ImportResource("classpath:cucumberBeanContext.xml")
public class CucumberConfiguration {
// nothing to do here
}