Using #Primary with spring-context-indexer - java

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

Related

How can I use two beans (or more) that return String in main program and replace them with another two while testing?

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

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.

How to add a bean in SpringBootTest

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.

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() {}

NoSuchBeanDefinitionException for dependencies of mocked beans

I am attempting to use mocks in my integration test and am not having much luck. I am using Spring 3.1.1 and Mockito 1.9.0, and the situation is as follows:
#Component
public class ClassToTest {
#Resource
private Dependency dependency;
}
and
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
Now, I want to do an integration test of ClassToTest using Spring's JavaConfig. This is what I have attempted, and it doesn't work:
#Test
#ContextConfiguration
public class ClassToTestIntegrationTest {
#Resource
private ClassToTest classToTest;
#Resource
private Dependency mockDependency;
#Test
public void someTest() {
verify(mockDependency).doStuff();
// other Mockito magic...
}
#Configuration
static class Config {
#Bean
public ClassToTest classToTest() {
return new ClassToTest();
}
#Bean
public Dependency dependency() {
return Mockito.mock(Dependency.class);
}
}
}
I have simplified my setup to make the question easier to understand. In reality I have more dependencies and only want to mock some of them - the others are real, based on config imported from my prod #Configuration classes.
What ends up happening is I get a NoSuchBeanDefinitionException saying that there are no beans of type NestedDependency in the application context. I don't understand this - I thought Spring would receive Mockito's mocked instance of Dependency and not even look at autowiring it. Since this isn't working I end up having to mock my entire object graph - which completely defeats the point of mocking!
Thanks in advance for any help!
I had the same problem and I found another solution.
When Spring instantiate all your beans, it will check if it's a Mockito Mock and in this case, I return false for injection property. To use it, just inject it in a Spring context
Code below:
public class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
private static final MockUtil mockUtil = new MockUtil();
public MockBeanFactory() {
super();
}
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockUtil.isMock(bean);
}
}
What Mockito does when mocking classes is it creates a subclass using cglib having some fancy name like: Dependency$EnhancerByMockito (IIRC). As you probably know, subclasses inherit fields from their parent:
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
public class Dependency$EnhancerByMockito extends Dependency{
//...
}
This means Spring still sees the field in base class when presented with mock. What you can do:
Use interfaces, which will cause Mockito to employ dynamic proxies rather than CGLIB-generated classes
Mock NestedDependency - I know it will just cascade the problem one level further
Disable #Resource annotation scanning for tests

Categories

Resources