I Have a bean defined in a configuration class:
#Configuration
public class Config {
#Value("${some-property}")
private String someProperty;
#Bean
public SomeBean someBean() {
return new SomeBean(someProperty);
}
}
How do I define a cucumber step definition to set the property some-property and let SomeBean to use it?
The problem I'm facing is that my beans are initialized before any steps happen. How do I reinitialize a bean, or refresh using the #RefreshScope maybe? Or can I start/restart the spring context in a later step?
Cucumber step:
#Given("I have some property set to (.*)")
public void someProperty(String someProperty) {
// Answer
}
And here's the empty step definition class to start Spring Context:
#SpringBootTest(classes = Application.class)
#DirtiesContext
#ActiveProfiles("it")
#AutoConfigureCache
#AutoConfigureTestEntityManager
#AutoConfigureWebMvc
#AutoConfigureMockMvc(secure = false)
#ImportAutoConfiguration
public class CucumberSpringContextBootstrapper implements En {
}
Related
I have two different beans for the same class with different configurations depending on the given profile.
#Bean
#Profile("!local")
public VaultPropertySource vaultPropertySource(ConfigurableApplicationContext context)
#Bean
#Profile("local")
public VaultPropertySource vaultPropertySourceLocal(ConfigurableApplicationContext context)
I have another bean that depends on VaultPropertySource instance.
#Component
#RequiredArgsConstructor
#DependsOn({"vaultPropertySource"})
public class VaultPropertyReader {
private final VaultPropertySource vaultPropertySource;
The problem is bean names are different and it only works on the first instance. How can I make it work on both profiles? Can I make it depend on the bean class instead of bean name?
Separate the profiles not on the bean but on the configuration class:
#Configuration
#Profile("!local")
class VaultConfiguration {
#Bean
public VaultPropertySource vaultPropertySource(ConfigurableApplicationContext context) {
// return real PropertySource
}
}
#Configuration
#Profile("local")
class LocalVaultConfiguration {
#Bean
public VaultPropertySource vaultPropertySource(ConfigurableApplicationContext context) {
// return local PropertySource
}
}
That probably helps.
I have a custom configuration class that I am loading using spring factories during bootstrap. The problem is that it is being overwritten by another similar configuration class coming from a spring ** starter package. I've tried excluding the second one, but it still loads. Also tried to set priorities, but that didn't work too.
Here's a snippet of my custom configuration class:
#Slf4j
#Configuration
#RequiredArgsConstructor
public class CustomAwsParamStorePropertySourceLocatorConfig implements PropertySourceLocator
...
And the one I'm trying to exclude that is coming from spring boot aws starter:
public class AwsParamStorePropertySourceLocator implements PropertySourceLocator {
The AwsParamStoreBootstrapConfiguration class has the ConditionalOnProperty annotation at the class level...
#Configuration(proxyBeanMethods = false)
#EnableConfigurationProperties(AwsParamStoreProperties.class)
#ConditionalOnClass({ AWSSimpleSystemsManagement.class, AwsParamStorePropertySourceLocator.class })
#ConditionalOnProperty(prefix = AwsParamStoreProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class AwsParamStoreBootstrapConfiguration {
private final Environment environment;
public AwsParamStoreBootstrapConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
AwsParamStorePropertySourceLocator awsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient,
AwsParamStoreProperties properties) {
if (StringUtils.isNullOrEmpty(properties.getName())) {
properties.setName(this.environment.getProperty("spring.application.name"));
}
return new AwsParamStorePropertySourceLocator(ssmClient, properties);
}
So if you configured the property aws.paramstore.enabled=false it should stop that configuration from creating the AwsParamStorePropertySourceLocator bean.
It's important to note, that would also stop the creation of the AWSSimpleSystemsManagement bean which is also created in the AwsParamStoreBootstrapConfiguration class, so if you require that bean, you may need to also create it in your custom Configuration class.
#Bean
#ConditionalOnMissingBean
AWSSimpleSystemsManagement ssmClient(AwsParamStoreProperties properties) {
return createSimpleSystemManagementClient(properties);
}
public static AWSSimpleSystemsManagement createSimpleSystemManagementClient(AwsParamStoreProperties properties) {
AWSSimpleSystemsManagementClientBuilder builder = AWSSimpleSystemsManagementClientBuilder.standard()
.withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration());
if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
builder.withRegion(properties.getRegion());
}
if (properties.getEndpoint() != null) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
properties.getEndpoint().toString(), null);
builder.withEndpointConfiguration(endpointConfiguration);
}
return builder.build();
}
The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.
Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test
Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})
What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.
I have a #Service annotated class which provides core functionality which I can use in all my projects:
#Service
public class MyService {}
and another one which extends it to implement project specific stuff:
#Service
public class ExtendedMyService extends MyService {}
Now I would like to configure a bean alias to be able to use #Qualifier("MyServiceAlias") when autowiring it using a property:
# MyService qualifier (default: myService)
myService.qualifier=extendedMyService
In XML it would look like:
<alias name="${myService.qualifier}" alias="MyServiceAlias" />
It is also discussed here, but I need to do it w/o XML, JavaConfig only.
Is it possible and how to realize?
There is an open Jira for this: https://jira.spring.io/browse/SPR-6736
The workaround is to use #Bean in #Configuration class:
#Configuration
public class AppConfig {
#Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public MyService myService() {}
}
If you want to use the placeholder, another workaround is to use #Bean in a #Configuration class using #Value and the Spring applicationContext.
#Configuration
public class AppConfig {
#Autowired
private ApplicationContext context;
#Bean
public MyService myService(#Value("${myService.qualifier}") String qualifier) {
return (MyService) context.getBean(qualifier);
}
}
NB : special consideration must be taken for the placeholder bean which must be loaded at the beginning (cf javadoc)
With small amount of configuration and one ImportBeanDefinitionRegistrar you can configure bean aliases via Java configuration. You can check bean-alias library project for reference - developed for the needs of my projects. Feel free to modify and/or copy the source into your own project in case the spring version used in it does not work with your setup.
Once you have the library on your path, you declare an alias through the annotation:
#Configuration
#BeanAlias(name = "fromName", alias = "toName")
public class ExampleConfiguration {
}
That's it.
How it works is that with the annotation we import a ImportBeanDefinitionRegistrar implementation
#Import(BeanAliasBeanRegistrar.class)
public #interface BeanAlias {
}
which registers the alias in the BeanDefinitionRegistry
class BeanAliasBeanRegistrar implements ImportBeanDefinitionRegistrar, PriorityOrdered {
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
...
registerAlias(registry, metadata.getAnnotationAttributes(BeanAlias.class.getName()));
}
private void registerAlias(BeanDefinitionRegistry registry, Map<String, Object> attributes) {
...
registry.registerAlias(name, alias);
}
}
I have the following configuration bean for a non web app
#Configuration
public class MyBeans {
#Bean
#Scope(value="prototype")
MyObject myObject() {
return new MyObjectImpl();
}
}
On the other side I have my class
public class MyCommand implements Command {
#Autowired
private MyObject myObject;
[...]
}
How can I make myCommand be autowired with the configuration in MyBeans without using XML so I can inject mocks in my other test classes?
Thanks a lot in advance.
With XML-based configuration you'd use the ContextConfiguration annotation. However, the ContextConfiguration annotation doesn't appear to work with Java Config. That means that you have to fall back on configuring your application context in the test initialization.
Assuming JUnit4:
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest{
private ApplicationContext applicationContext;
#Before
public void init(){
this.applicationContext =
new AnnotationConfigApplicationContext(MyBeans.class);
//not necessary if MyBeans defines a bean for MyCommand
//necessary if you need MyCommand - must be annotated #Component
this.applicationContext.scan("package.where.mycommand.is.located");
this.applicationContext.refresh();
//get any beans you need for your tests here
//and set them to private fields
}
#Test
public void fooTest(){
assertTrue(true);
}
}