Test uses internal ContextConfiguration of other test - java

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.

Related

Spring Junit Configuration class not loaded

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;

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.

Using #Primary with spring-context-indexer

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

Is there a way to use Autowired constructor in JUnit test using Spring or Spring Boot?

Assume, that I have a test configuration with several Spring beans, that are actually mocked and I want to specify the behavior of those mocks inside JUnit test suite.
#Profile("TestProfile")
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {
"some.cool.package.*"})
public class IntegrationTestConfiguration {
#Bean
#Primary
public Cool cool() {
return Mockito.mock(Cool.class);
}
}
// ...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ActiveProfiles("TestProfile")
public class CoolIntegrationTest {
private final Cool cool;
#Autowired
public CoolIntegrationTest(Cool cool) {
this.cool = cool;
}
#Test
public void testCoolBehavior {
when(cool.calculateSomeCoolStuff()).thenReturn(42);
// etc
}
}
If I run this test I will get:
java.lang.Exception: Test class should have exactly one public zero-argument constructor
I know the workaround like use Autowired fields in tests, but I wonder if there a way to use Autowired annotation in JUnit tests?
It's not the autowiring that's the problem, it's the no-arg constructor. JUnit test classes should have a single, no argument constructor. To achieve what you are attempting to do, you should do the following:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ActiveProfiles("TestProfile")
#ContextConfiguration(classes = {IntegrationTestConfiguration.class})
public class CoolIntegrationTest {
#Autowired
private final Cool cool;
#Test
public void testCoolBehavior {
when(cool.calculateSomeCoolStuff()).thenReturn(42);
// etc
}
}
The contextConfiguration annotation tells spring which config to use for the test, and autowiring the field instead of the constructor will allow you to test your spring bean.
To run a test using Spring you have to add #RunWith(SpringRunner.class) and make sure that your class is added to the classpath. There are a few ways to do it. I.e.
Add class to MVC configuration #WebMvcTest({Class1.class, Class2.class}) or use #ContextConfiguration.
But I see your code, I suppose that it would be easier just use #Mock or #MockBean to mock your beans. It will be much easier.
JUnit requires the Test case to have a no-arg constructor, so, since you don't have one, the exception happens before the wiring process.
So Constructor-Autowiring just doesn't work in this case.
So what to do?
There are many approaches:
The easiest one (since you have spring) is taking advantage of #MockBean annotation:
#RunWith(SpringRunner.class)
#SpringBootTest
....
class MyTest {
#MockBean
private Cool cool;
#Test
void testMe() {
assert(cool!= null); // its a mock actually
}
}
Besides args constructor you need to have additional one no-args constructor. Try add it and check if this exception still occurcs.
#Autowired
public CoolIntegrationTest(Cool cool) {
this.cool = cool;
}
public CoolIntegrationTest() {}

Spring #PostConstruct depending on #Profile

I'd like to have multiple #PostConstruct annotated methods in one configuration class, that should be called dependent on the #Profile. You can imagine a code snipped like this:
#Configuration
public class SilentaConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(SilentaConfiguration.class);
#Autowired
private Environment env;
#PostConstruct #Profile("test")
public void logImportantInfomationForTest() {
LOG.info("********** logImportantInfomationForTest");
}
#PostConstruct #Profile("development")
public void logImportantInfomationForDevelopment() {
LOG.info("********** logImportantInfomationForDevelopment");
}
}
However according to the javadoc of #PostConstruct I can only have one method annotated with this annotation. There is an open improvement for that in Spring's Jira https://jira.spring.io/browse/SPR-12433.
How do you solved this requirement? I can always split this configuration class into multiple classes, but maybe you have a better idea/solution.
BTW. The code above runs without problems, however both methods are called regardless of the profile settings.
I solved it with one class per #PostConstruct method. (This is Kotlin but it translates to Java almost 1:1.)
#SpringBootApplication
open class Backend {
#Configuration
#Profile("integration-test")
open class IntegrationTestPostConstruct {
#PostConstruct
fun postConstruct() {
// do stuff in integration tests
}
}
#Configuration
#Profile("test")
open class TestPostConstruct {
#PostConstruct
fun postConstruct() {
// do stuff in normal tests
}
}
}
You can check for profile with Environment within a single #PostContruct.
An if statement would do the trick.
Regards,
Daniel

Categories

Resources