Overriding spring #Configuration in an integration test - java

I have a spring boot configuration class like this:
#Configuration
public class ClockConfiguration {
#Bean
public Clock getSystemClock() {
return Clock.systemUTC();
}
}
and I have some integration tests like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
}
and tests like this:
public class MiscTests extends AbstractIntegrationTest{
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
}
I want to be able to offset the clock bean to run some tests at different times on the day. How do I do this?
NOTE: I see several stack overflow answers similar to this, but I can't get them to work.
Based on other responses, it appears the solution should be something like:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#Configuration
class MyTestConfiguration {
#Bean
public Clock getSystemClock() {
Clock realClock = Clock.systemDefaultZone();
return Clock.offset(realClock, Duration.ofHours(9));
}
}
}
But nothing happens there. Do I need to #Import something? do I need to #Autowired something?
Thanks!

As you are using Spring Boot you can take advantage of the #MockBean annotation:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#MockBean
private Clock clockMock;
}
Then you can stub public methods of that bean an each of the tests accordingly and uniquely:
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
when(clockMock.getTime()).thenReturn(..);
}
As per javadoc of #MockBean:
Any existing single bean of the same type defined in the context will
be replaced by the mock.

it is the #TestConfiguration annotation that you need https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/TestConfiguration.html
#RunWith(SpringRunner.class)
public class ClockServiceImplIntegrationTest {
#TestConfiguration
static class TestOverridingClockServiceConfiguration {
#Bean
public ClockService clockService() {
return new ClockServiceImpl();
}
}
#Autowired
private ClockService clockService;
#MockBean
private ClockRepository clockRepository;
// write test cases here
}
In the case you have existing configuration you c

Related

Spring Bean override with annotations in parent-child relationship

I have a two classes which are in a parent-child relation, the second one being a mock of the first.
public class A {
public void doSomething(){...};
}
public class MockA extends A {
#Override
public void doSomething() {...};
}
I also have two #Configuration classes one for development environment and one for test, the test one just mocks a couple of behaviors, but it imports the development one. Either way, I want that in test environment for MockA to be injected and in development for class A to be injected in other services which autowire it.
I can do that if I overwrite the bean in the test configuration. The following will work:
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}
However, I do not want to create the beans in the #Configuration class. I want to put the #Component annotation on each one and let them be injected in services correctly.
I've tried two approaches
1) Creating a dummy annotation, adding the annotation on the class A and trying to exclude the bean from the TestApplicationConfig.
#Configuration
#ComponentScan(
basePackages = {
"murex.connectivity.tools.interfaces.monitor.cellcomputer",
"murex.connectivity.tools.interfaces.monitor"
}, excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
MyAnnotation.class))
public class TestApplicationConfig {
}
2) using #Component and #Primary annotations on the MockA class. My logic being that in the case both are present (which will happen only on the test case, because only then the MockA is scanned), the MockA will be injected everywhere. But this does not happen.
I am wondering if I am doing something wrong or is this a limitation from Spring? The #Primary annotation seems to be constructed exactly for this specific case, am I mistaken? Is the fact that the two classes have a parent-child relationship that is the problem?
L.E. Using two different profiles will work. I am more curious if my understanding of the two presented approaches is correct and/or if this is a limitation on Spring using #Component annotations
Tried to combine two suggested approaches. It worked for me:
#Component
#Primary
public class ClassA {
public void doSomething() {
System.out.println("A");
}
}
#Component
public class ClassB extends ClassA {
#Override
public void doSomething() {
System.out.println("B");
}
}
#Component
public class ClassC {
#Autowired
public ClassA component;
}
All three classes are in same package.
#Configuration
#ComponentScan(value = "com.test", excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
Primary.class))
public class RandomConfig {
}
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {RandomConfig.class})
public class RandomTest {
#Autowired
ClassC c;
#Test
void when_then() {
//prints "B"
c.component.doSomething();
}
}
So RandomConfig excludes all #Primary beans, whereas production config uses only #Primary
You can use profiles.
#Profile("!test")
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
And for test cases:
in test resources in application.properties: spring.profiles.active=test or on test class #ActiveProfiles("test") and create configuration:
#Profile("test")
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}

Delegate mocks to separate class

Instead of having this:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {Application.class}, webEnvironment =
SpringBootTest.WebEnvironment.DEFINED_PORT)
public abstract class AbstractIT {
#MockBean
private FooAdapter fooAdapter;
#MockBean
private BarAdapter barAdapter;
public void mockFoo() {
FooResponse dto = new FooResponse();
when(fooAdapter.fooRequest()).thenReturn(dto);
}
I want to have that:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {Application.class}, webEnvironment =
SpringBootTest.WebEnvironment.DEFINED_PORT)
public abstract class AbstractIT {
#MockBean/Autowired?
MockProvider mockProvider;
class MockProvider {
#MockBean
private FooAdapter fooAdapter;
#MockBean
private BarAdapter barAdapter;
public void mockFoo() {
FooResponse dto = new FooResponse();
when(fooAdapter.fooRequest()).thenReturn(dto);
}
However, I don't know if that is even possible using Mockito with SpringRunner. Since we have a lot of adapters (like 10), I do not want to pollute the AbstractIT too much, hence I'd like to delegate the initialization and concrete mocking of those dependencies out to another class taking care of that.
You have to specify that provider class in the #ContextConfiguration for each test you want to use it for:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = {MockProvider.class} // <-- this
public abstract class AbstractIT {
You have to make sure to make the provider a configuration class:
#Configuration
public class MockProvider {
As per #MockBean documentation:
The annotation can be used directly on test classes, on fields within
your test, or on #Configuration classes and fields.

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.

Beans are not Autowired during tests (java.lang.NullPointerException)

my application normally works fine, but when I run tests, or build application by maven, application is shutting down due tests with errors java.lang.NullPointerException. I debugged it and find out my that my beans in service layer are not Autowired and they are null.
Here is my class with tests:
public class CompanyServiceSimpleTest {
private CompanyService companyService;
#Before
public void setUp() {
companyService = new CompanyServiceImpl();
}
// Here is sample test
#Test
public void testNumberOfCompanies() {
Assert.assertEquals(2, companyService.findAll().size());
}
}
companyService is initialized, but beans in it not. Here is CompanyServiceImpl:
#Service
public class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository; // is null
#Autowired
private NotificationService notificationService; // is null
#Override
public List<CompanyDto> findAll() {
List<CompanyEntity> entities = companyRepository.find(0, Integer.MAX_VALUE);
return entities.stream().map(Translations.COMPANY_DOMAIN_TO_DTO).collect(Collectors.toList());
}
// ... some other functions
}
So when is called companyRepository.find() applications crashes. Here is repository class:
#Repository
#Profile("inMemory")
public class CompanyInMemoryRepository implements CompanyRepository {
private final List<CompanyEntity> DATA = new ArrayList<>();
private AtomicLong idGenerator = new AtomicLong(3);
#Override
public List<CompanyEntity> find(int offset, int limit) {
return DATA.subList(offset, Math.min(offset+limit, DATA.size()));
}
// ... some others functions
}
I have set up profile for that service but I had that VM options in Idea:
-Dspring.profiles.active=develpment,inMemory
So it should works.
To make autowiring work it has to be a Spring integration test. You have to anotate your test class with:
#RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(classes = {MyApplicationConfig.class})
If it is a Spring Boot app e.g.:
#RunWith(SpringJUnit4ClassRunner.class) and #SpringBootTest(classes = {MyApp.class, MyApplicationConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
More on this topic: http://www.baeldung.com/integration-testing-in-spring and http://www.baeldung.com/spring-boot-testing
You are not configuring Spring in your TestClasses, so it's impossible to inject anything...
Try configurin your class with
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "path to your config xml" })
A little example:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:config/applicationContext.xml"})
public class MyTest {
#Autowired
private MyClass myInjectedClass;
#Test
public void someTest() {
assertNotNull(myInjectedClass);
}
}

How to inject mock into #Service that has #Transactional

I have any issue in my unit test where I have something along the lines of this. The mock injection get overridden on the someService if the blargh function is annotated with Transactional. If I remove the Transactional the mock stays there. From watching the code it appears that Spring lazily loads the services when a function in the service is annotated with transactinal, but eagerly loads the services when it isn't. This overrides the mock I injected.
Is there a better way to do this?
#Component
public class SomeTests
{
#Autowired
private SomeService someService;
#Test
#Transactional
public void test(){
FooBar fooBarMock = mock(FooBar.class);
ReflectionTestUtils.setField(someService, "fooBar", fooBarMock);
}
}
#Service
public class someService
{
#Autowired FooBar foobar;
#Transactional // <-- this causes the mocked item to be overridden
public void blargh()
{
fooBar.doStuff();
}
}
Probably you could try to implement your test in the following way:
#Component
#RunWith(MockitoJUnitRunner.class)
public class SomeTests
{
#Mock private FooBar foobar;
#InjectMocks private final SomeService someService = new SomeService();
#Test
#Transactional
public void test(){
when(fooBar.doStuff()).then....;
someService.blargh() .....
}
}
I could not try it right now as don't have your config and related code. But this is one of the common way to test the service logic.
Use the Spring #Profile functionality - beans can be associated to a certain group, and the group can be activated or deactivated via annotations.
Check this blog post and the documentation for more detailed instructions, this is an example of how to define production services and two groups of mock services:
#Configuration
#Profile("production")
public static class ProductionConfig {
#Bean
public InvoiceService realInvoiceService() {
...
}
...
}
#Configuration
#Profile("testServices")
public static class TestConfiguration {
#Bean
public InvoiceService mockedInvoiceService() {
...
}
...
}
#Configuration
#Profile("otherTestServices")
public static class OtherTestConfiguration {
#Bean
public InvoiceService otherMockedInvoiceService() {
...
}
...
}
And this is how to use them in the tests:
#ActiveProfiles("testServices")
public class MyTest extends SpringContextTestCase {
#Autowired
private MyService mockedService;
// ...
}
#ActiveProfiles("otherTestServices")
public class MyOtherTest extends SpringContextTestCase {
#Autowired
private MyService myOtherMockedService;
// ...
}
I have the exact same problem and I solve it by using Mockito.any() for the arguments
eg:
when(transactionalService.validateProduct(id)).thenReturn("")
=> when(transactionalService.validateProduct(Mockito.any())).thenReturn("")

Categories

Resources