Spring Junit Configuration class not loaded - java

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;

Related

Spring Boot Cucumber tests could not resolve placeholder 'random.uuid'

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.

What's the difference between #ComponentScan and #Bean in a context configuration?

There are at least 2 approaches to put Spring beans into a context configutation:
Declare a method with #Bean inside the configuration class.
Put #ComponentScan on the configuration class.
I was expecting that there are no difference between the 2 approaches in terms of the resulting Spring beans.
However, I found an example to demonstrate the difference:
// UserInfoService.java
public interface UserInfoService
{
#PreAuthorize("isAuthenticated()")
String getUserInfo ();
}
// UserInfoServiceTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(classes = TestSecurityContext.class),
#ContextConfiguration(classes = UserInfoServiceTest.Config.class)
})
public class UserInfoServiceTest
{
#Configuration
public static class Config
{
#Bean
public UserInfoService userInfoService ()
{
return new UserInfoServiceImpl();
}
}
#Autowired
private UserInfoService userInfoService;
#Test
public void testGetUserInfoWithoutUser ()
{
assertThatThrownBy(() -> userInfoService.getUserInfo())
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
}
#Test
#WithMockUser
public void testGetUserInfoWithUser ()
{
String userInfo = userInfoService.getUserInfo();
assertThat(userInfo).isEqualTo("info about user");
}
The above code is to test the security annotation in the service UserInfoService. However, it will fail on testGetUserInfoWithoutUser(). The reason is that the bean userInfoService didn't get proxied by Spring Security. Therefore, the call userInfoService.getUserInfo() didn't get blocked by the annotation #PreAuthorize("isAuthenticated()").
However, if I replace the #Bean annotation with #ComponentScan, everything will start to work. That is, the bean userInfoService will get proxied and the call userInfoService.getUserInfo() will be blocked by the #PreAuthorize annotation.
Why are the approaches between the #Bean and #ComponentScan different? Did I miss something?
P.S. The full example is here and the above mentioned fix is here.
It is because #ContextHierarchy will create multiple spring contexts with parent children hierarchy. In your case, TestSecurityContext defines the bean configuration for the parent context while UserInfoServiceTest.Config defines for the children context.
If there is no #ComponentScan on the UserInfoServiceTest.Config , the security related beans are defined in the parent context which are invisible to the UserInfoService bean in the children context , and hence it does not get proxied by Spring Security.
On the other hand , if you define #ComponentScan on the UserInfoServiceTest.Config , it will also scan all the #Configuration beans from the package that containing UserInfoService (and all of its sub package) . Because TestSecurityContext is also inside this package and hence it get scanned and the security related beans are also configured for the children context. UserInfoService in children context will then get proxied by Spring Security.(Note : In this case ,both the parent and children context has their own set of security related beans)
By the way , if you just need a single context , you can simply use #ContextConfiguration :
#ContextConfiguration(classes= {TestSecurityContext.class,UserInfoServiceTest.Config.class})
public class UserInfoServiceTest {
public static class Config {
#Bean
public UserInfoService userInfoService() {
return new UserInfoServiceImpl();
}
}
}

Test uses internal ContextConfiguration of other test

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.

Getting "At least one JPA metamodel must be present" with #WebMvcTest

I'm fairly new to Spring, trying to do some basic integration tests for a #Controller.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
}
but I'm getting
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Unlike most people posting this error, I don't want to use JPA for this. Am I trying to use #WebMvcTest incorrectly? How can I track down the Spring magic that's inviting JPA to this party?
Remove any #EnableJpaRepositories or #EntityScan from your SpringBootApplication class instead do this:
package com.tdk;
#SpringBootApplication
#Import({ApplicationConfig.class })
public class TdkApplication {
public static void main(String[] args) {
SpringApplication.run(TdkApplication.class, args);
}
}
And put it in a separate config class:
package com.tdk.config;
#Configuration
#EnableJpaRepositories(basePackages = "com.tdk.repositories")
#EntityScan(basePackages = "com.tdk.domain")
#EnableTransactionManagement
public class ApplicationConfig {
}
And here the tests:
#RunWith(SpringRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
}
I had the same problem. #WebMvcTest looks for a class annotated with #SpringBootApplication (in the same directory or higher up in your app structure if it doesn't find one). You can read how this works # https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests.
If your class annotated with #SpringBootApplication also has #EntityScan /#EnableJpaRepositories this error occurs. Because you have these annotations with #SpringBootApplication and you are mocking the service ( so actually not using any JPA ). I found a workaround which may not be the prettiest, but works for me.
Place this class in your test directory ( the root ). #WebMvcTest will find this class before your actual Application class. In this class you don't have to add #EnableJpaRepositories/#EntityScan.
#SpringBootApplication(scanBasePackageClasses = {
xxx.service.PackageMarker.class,
xxx.web.PackageMarker.class
})
public class Application {
}
And your test will look the same.
#RunWith(SpringRunner.class)
#WebMvcTest
#WithMockUser
public class ControllerIT {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
public void testName() throws Exception {
// when(service.xxx(any(xxx.class))).thenReturn(xxx);
// mockMvc.perform(post("/api/xxx")...
// some assertions
}
}
Hope this helps!
Alternatively, you can define a custom configuration class inside your test case, including only the controller (plus its dependencies), to force Spring to use this context.
Please note, you'll still have access to MockMvc and other goodness in your test case, if it's WebMvcTest annotated.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
#Configuration
#ComponentScan(basePackageClasses = { DemoController.class })
public static class TestConf {}
Add #MockBean(JpaMetamodelMappingContext.class) to above of class DemoControllerIntegrationTests:
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
#MockBean(JpaMetamodelMappingContext.class)
public class DemoControllerIntegrationTests {
...
}
Because you have not used a database in your test, Spring throws this exception. By mocking JpaMetamodelMappingContext class you will bypass the needed metamodel.
If anyone uses Spring boot and don't want to remove #EntityScan and #EnableJpaRepositories you can remove #WebMvcTest annotation from your test class and add the following instead:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
//...
}
and you will be able to autowire MockMvc and use it.

Using different java based spring context configurations with cucumber

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
}

Categories

Resources