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
}
Related
I see a lot of posts surrounding this error message, but none with a solution that's worked for me.
I'm writing a Spring application that has required me to configure multiple data sources. I got all of those working, but now I need to import a module my teammates built that uses an additional data source and several repositories. They've been using this module in their code and it seems to be working for them, but when I try to run my code I get the following error (some details obscured for work reasons):
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.company.teammodule.repositories.StatsRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
//I can't paste code, so there may be typos...
Spring Application Class:
package com.company.mymodule;
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages={
"com.company.mymodule",
"com.company.mymodule.utils",
"com.company.mymodule.configuration",
"com.company.teammodule.repositories",
//I've been fiddling with packages here; I've added every package from the teammatemodule app and still get the error described})
#public class MyModuleApplication
{
public static void main(String[] args) { SpringApplication.run(MyModuleApplication.class, args); }
}
My class that uses the teammatemodule:
package com.company.mymodule.utils;
#Component
public class TeammateModuleUtil {
#Autowired
private TeammateModuleConfiguration config; //This class contains an object of another class which references the problem repository
#Value("${applicationName})
private final String applicationName;
public TeammateModuleUtil()
{
try {
config.setApplicationName(applicationName);
config.loadConfiguration();
}
catch (Exception e) {
//Error handling
}
}
}
The TeammateModuleConfiguration class:
package com.company.teammatemodule
#Component
public class TeammateModuleConfiguration extends ConfigurationBase {
#Autowired
TeammateModuleServiceData data; //This class contains a reference to the problem repository
#Autowired
Utilities util;
#Autowired
ConfigurationRepository configurationRepository;
public void loadConfiguration() throws Exception {
try {
this.alConfigure = this.configurationRepository.findByConfigureIdApplicationName(this.applicationName);
} catch (Exception e) {
//Error handling
}
}
}
Here's the class that has the problem reference:
package com.company.teammatemodule;
#Component
public class TeammateModuleServiceData {
#Autowired
StatsRepository statsRepository //This is the repo the code can't find a bean for
#Autowired
MessageAuthorizationRepository messageAuthorizationRepository;
#Autowired
LogMessagesRepository logMessagesRepository;
#Autowired
Utilities util;
//Class methods
}
And here's the repository class for good measure:
package com.company.teammatemodule.repositories;
#Repository
public interface StatsRepository extends JpaRepository<Stats, StatsId> {
#Procedure(
procedureName = "schema.log_service_stats",
outputParameterName = "o_stats_id"
)
String logServiceStats(#Param("i_request_payload") String var1, #Param("i_response_payload") String var2, #Param("i_operation_name") String var3, #Param("i_stats_id") String var4);
#Procedure(procedureName = "schema.update_service_stats)
void updateServiceStats(#Param("i_request_payload") String var1, #Param("i_response_payload") String var2, #Param("i_operation_name") String var3, #Param("i_stats_id) String var4);
}
The above repository is the one that can't be found when I try to launch the application. What I've checked and tried:
The main application has the #SpringApplication annotation, and all other involved classes have either #Component, #RestController, or #Repository, so I don't think it's an issue of some component not being labeled a bean...
None of the other repositories in teammatemodule are giving me a problem, but I'm guessing StatsRepository is just the first repository to be referenced
The only properties in my properties file are applicationName and data sources unrelated to the teammatemodule. I'm not listing any packages or exclusions there.
I've specified all packages in #ComponentScan ( basepackages = {//packages} ) as well as #SpringApplication( scanBasePackages = {//packages} )
I've tried excluding spring's autoconfigure by using the following annotations in my SpringAplication class:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
So I don't think it's a missing #Component or other bean annotation, and to the best of my ability I've specified the package in which the repository bean is located and overridden Spring's built-in autoconfiguration tools... But the app still can't find the bean.
Thanks for your help!
It looks like this might just be a simple misspelling:
com.company.teammodule.repositories <- Component Scan
com.company.teammatemodule.repositories <- Package specified in Repository class
I am working on multi module maven project with SpringBoot framework.
The project is divided in 4 modules: rest module,service module,repository module, domain module.
I am trying to write a unit test for a configuration class in java which is located in the service module.
I have simplified the case to get rid of business logic complexity. The configuration class is like below:
#Configuration
#ConfigurationProperties(prefix = "x.y.feature", ignoreInvalidFields = false)
public class FeatureConfig {
private String featureUrl;
public String getFeatureUrl() {
return featureUrl;
}
public void setFeatureUrl(String FeatureUrl) {
this.featureUrl = featureUrl;
}
}
The properties file is application.properties.
x.y.feature.featureUrl=featureUrl
And below is the unit test that is not working.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { FeatureConfigTest.class })
public class FeatureConfigTest {
#Autowired
private FeatureConfig featureConfig;
#Test
public void testgetFeatureUrl() {
String expected ="featureUrl";
assertEquals(expected,featureConfig.getFeatureUrl());
}
}
When i run the unit test , it throws the exception below:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
The problem is that you are trying to use object of FeatureConfig
from the application context. You haven't initialized the application context.
When you use the #Autowire annotation, you are mapping field 'featureConfig'
with instance of type FeatureConfig class located in the application context.
To bypass this error you need initialize the application context.
First thing , create static class inside the test class that will help us not to load the
whole application context. Because for this test case you don't need to load the whole application.
First thing , create static class inside the test class that will help us not to load the
whole application context. Because for this test case you don't need to load the whole application.
#EnableConfigurationProperties(FeatureConfig.class)
public static class TestConfiguration {
}
After that you start the application context , but with passing as configuration the static class
you created. This is done not to load the whole application context.
#SpringBootTest(classes = { FeatureConfigTest.TestConfiguration class })
Copy and paste the changes below in your test class and everything should work ok.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { FeatureConfigTest.TestConfiguration class })
public class FeatureConfigTest {
#Autowired
private FeatureConfig featureConfig;
#Test
public void testgetFeatureUrl() {
String expected ="featureUrl";
assertEquals(expected,featureConfig.getFeatureUrl());
}
#EnableConfigurationProperties(FeatureConfig.class)
public static class TestConfiguration {
}
}
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.
We have a Spring based JUnit test class which is utilizing an inner test context configuration class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceTest.Config.class)
public class ServiceTest {
#Test
public void someTest() {
...
#Configuration
#PropertySource(value = { "classpath:application.properties" })
#ComponentScan({ "..." })
public static class Config {
...
New functionalities have been recently introduced to the Service class, for which the concerned tests should be added to ServiceTest. However these would also require a different test context configuration class to be created (the internals of the existing Config class are fairly complex and change it to serve both old and new tests seems to be be extremely difficult if possible at all)
Is there a way to achieve that certain test methods in one test class would use one config class and other methods would use another? #ContextConfiguration seems to be applicable only on class level, so solution could be to create another test class for the new tests which would utilize its own context configuration class; but it would mean that the same Service class is being covered via two different test classes
With Aaron's suggestion of manually building the context I couldn't find any good examples so after spending some time getting it working I thought I'd post a simple version of the code I used in case it helps anyone else:
class MyTest {
#Autowired
private SomeService service;
#Autowired
private ConfigurableApplicationContext applicationContext;
public void init(Class<?> testClass) throws Exception {
TestContextManager testContextManager = new TestContextManager(testClass);
testContextManager.prepareTestInstance(this);
}
#After
public void tearDown() throws Exception {
applicationContext.close();
}
#Test
public void test1() throws Exception {
init(ConfigATest.class);
service.doSomething();
// assert something
}
#Test
public void test2() throws Exception {
init(ConfigBTest.class);
service.doSomething();
// assert something
}
#ContextConfiguration(classes = {
ConfigATest.ConfigA.class
})
static class ConfigATest {
static class ConfigA {
#Bean
public SomeService someService() {
return new SomeService(new A());
}
}
}
#ContextConfiguration(classes = {
ConfigBTest.ConfigB.class
})
static class ConfigBTest {
static class ConfigB {
#Bean
public SomeService someService() {
return new SomeService(new B());
}
}
}
}
I use these approaches when I'm have to solve this:
Manually build the context in a setup method instead of using annotations.
Move the common test code to a base class and extend it. That allows me to run the tests with different spring contexts.
A mix of the two above. The base class then contains methods to build spring contexts from fragments (which the extensions can override). That also allows me to override test cases which don't make sense or do extra pre/post work in some tests.
Keep in mind that annotations only solve generic cases. You'll have to replicate some or all of their work when you leave the common ground.
Hitting an odd issue with my spring based integration tests. Note that I'm not so much unit testing a spring IoC app as I'm using Spring to auto-inject properties into my test configuration. Also - we're not using any context configuration xml, everything is configured programatically.
I got the framework all set up and running with a single test before handing it off to the team tester, auto-injection was working fine. However, as soon as he added a second test class, auto-injection of my configuration field just stops working. I've cloned his branch and verified the behavior; Commenting out his class OR my original class causes the auto-injection to return to normal function.
Anybody ever seen this? Relevant code snippets below. I've tried moving around the #ContextConfiguration annotation but it didn't really help. It seems like Spring is having trouble deciding where the TestProperties object should come from. The field I've commented on below is coming up null when I run the tests, but only if there are two test classes enabled.
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public abstract class BaseIntegrationTest
extends AbstractTestNGSpringContextTests
{
#Autowired
protected MockServerClient mockServer;
// I get an error in IntelliJ Pro on this field.
// Could not autowire. There is more than one bean of 'TestProperties' type. Beans: getTestProperties, testProperties
// I've also tried #Qualifier("getTestProperties") and #Qualifier("testProperties")
// which both resolve the error in IntelliJ but the TestProperties field is still null when running the tests.
#Autowired
protected TestProperties properties;
//...
}
public class TestGetMuIT
extends BaseIntegrationTest
{
private ManagementUnitClient managementUnitClient;
#BeforeMethod(alwaysRun = true)
public void setup()
{
String endpoint = properties.getServiceBaseUri(); //NullPointerException thrown here during test run
}
}
Other relevant classes:
#Configuration
public class RootConfig
{
protected static final Logger LOGGER = LoggerFactory.getLogger(RootConfig.class);
private PropertyService propertyService;
private TestProperties testProperties;
#Bean
public PropertyService propertyService() throws IOException {
if (propertyService == null) {
propertyService = new DynamoDbPropertyService(config.getPropertiesConfig());
propertyService.initialize();
}
return propertyService;
}
#Bean
public TestProperties getTestProperties() throws IOException {
if (testProperties == null) {
testProperties = new TestProperties();
}
}
And the actual TestProperties class:
#Component
public class TestProperties
{
public static class Default {
public static final String BASE_URI = "http://localhost:8055";
}
#Autowired
private PropertyService propertyService;
public String getServiceBaseUri()
{
return propertyService.getPropertyRegistry().getString(TestConfigKey.LocalServiceBaseUri.key(), Default.BASE_URI);
}
}
And these are my spring config annotations:
#Configuration
//Note: TestProperties.class is a recent addition. I added that and commented out the #Bean getTestProperties() method in RootConfig as a troubleshooting step
#Import({RootConfig.class, TestProperties.class})
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.inin.wfm.it"},
excludeFilters = #ComponentScan.Filter(value = com.inin.wfm.it.config.ConfigPackageExcludeFilter.class, type = FilterType.CUSTOM))
public class TestConfig {\\... }
Again all of the above works just fine if there's only one test file (and it doesn't matter which of the two I comment out). Anybody know what I'm doing wrong in my configuration?