Init spring boot mocks before they are injected - java

Is there a way to initialise mocks before they are injected into another component?
To give an example I have following classes:
#Service
SomeService {
#Autowired
public SomeService(SomeConfig config)
}
#Configuration
#Getter
#Setter
public class SomeConfig {
private String someValue;
}
In my test I'm doing the following:
#MockBean
SomeConfig someConfig;
#Autowired
SomeService someService;
The problem is, that the SomeService constructor is already accessing SomeConfig members, before I can even initialise it with when(someConfig.getSomeValue()).thenReturn("something"), resulting in a NullPtrException.
Is there a hook that gets executed before SomeService is instantiated?

You could manually setup your Service in a setup method.
Just make sure to exclude your SomeService from classpath scanning in those tests.
SomeServiceTest {
#MockBean
SomeConfig someConfig;
SomeService someService;
#BeforeEach
public void setup(){
// init mocks
// setup other stuff
someService = new SomeService(someConfig);
}
}

I assume that the SomeService looks like:
#Service
SomeService {
private final SomeConfig config
#Autowired
public SomeService(SomeConfig config) {
this.someConfig = someConfig;
// some logic with config getters
}
}
You should use annotation
#PostConsturct in a separate method and put the usage of config getters there, like:
#PostConstruct
private void postConstruct() {
// some logic with config getters
}

Related

How to mock class with #ConfigurationProperties in Spring Boot

I have a class that Autowires another class with #ConfigurationProperties.
Class with #ConfigurationProperties
#ConfigurationProperties(prefix = "report")
public class SomeProperties {
private String property1;
private String property2;
...
Class that Autowires above class SomeProperties
#Service
#Transactional
public class SomeService {
....
#Autowired
private SomeProperties someProperties;
.... // There are other things
Now, I want to test SomeService class and in my test class when I mock SomeProperties class, I am getting null value for all the properties.
Test class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SomeProperties.class)
#ActiveProfiles("test")
#EnableConfigurationProperties
public class SomeServiceTest {
#InjectMocks
private SomeService someService;
#Mock // I tried #MockBean as well, it did not work
private SomeProperties someProperties;
How can I mock SomeProperties having properties from application-test.properties file.
You are not mocking SomeProperties if you intend to bind values from a properties file, in which case an actual instance of SomeProperties would be provided.
Mock:
#RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
#InjectMocks
private SomeService someService;
#Mock
private SomeProperties someProperties;
#Test
public void foo() {
// you need to provide a return behavior whenever someProperties methods/props are invoked in someService
when(someProperties.getProperty1()).thenReturn(...)
}
No Mock (someProperties is an real object that binds its values from some propertysource):
#RunWith(SpringRunner.class)
#EnableConfigurationProperties(SomeConfig.class)
#TestPropertySource("classpath:application-test.properties")
public class SomeServiceTest {
private SomeService someService;
#Autowired
private SomeProperties someProperties;
#Before
public void setup() {
someService = new someService(someProperties); // Constructor Injection
}
...
You need to stub all the property values in #Test/#Before methods of SomeServiceTest.java like :
ReflectionTestUtils.setField(someProperties, "property1", "value1");
ReflectionTestUtils.setField(object, name, value);
You can also mock the reponse for dependent classes via Mockito.when()
If you are using #Mock , you need to stub the values as well. By default the value will be null for all the properties.

Can I inject mocks into a prototype bean with Autowired constructor?

Is it possible to inject a mock service into a prototype bean using the #Autowired constructor? I realize I could switch to setter injection but I would prefer to use the constructor if possible.
#Component
#Scope(value = "prototype")
public class Prototype {
private DependantService dependantService;
#Autowired
public Prototype(DependantService dependantService) {
this.dependantService = dependantService;
}
}
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
// How can I inject the mock service?
ctx.getBean(Prototype.class);
}
}
Turns out there is an overloaded version of the getBean method that accepts arguments. I would downvote my on question if I could.
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
Prototype p = ctx.getBean(Prototype.class, dependantService);
// Test p
}
}
If you want to speed up your unit tests, [and do true isolated unit testing,] I suggest taking a look at the #InjectMocks mockito annotation. #SpringBootTest fires up the Spring container which is pretty cumbersome.
#Controller
public class MyController {
#Inject
private Logger log;
public methodThatNeedsTesting(){
log.info("hey this was called");
}
}
#TestInstance(Lifecycle.PER_CLASS)
#ExtendWith({ MockitoExtension.class })
class MyControllerTest {
#Mock
private Logger log;
#InjectMocks
private MyController myController;
#Test
void test_methodThatNeedsTesting() throws Exception {
myController.methodThatNeedsTesting();
// myController will not throw an NPE above because the log field has been injected with a mock
}

#Repository instance is null when invoking a method using a #Service instance from a unit test

My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
So that the repository instance actually interacts with a DB, and I dont just mock return values.
The problem is that when I run my unit test, the repository instance inside the service instance is null.
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
This is the console output when running my unit test:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
My unit test class:
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
My service class:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
My repository class:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
My entity class:
#Entity
public class MyEntity {
#Id
#GeneratedValue
public Long id;
}
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
Basically yes :)
You need to initialise a Spring Context by Annotating your Testclass with #SpringBootTest
The other Problem you have is that you create your MyService Object manually.
By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:
#SpringBootTest
public class MyTest {
#Autowired
private MyService myService;
#Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
To use #MockBean annotation, you have to use SpringRunner to run the test. Use #RunWith Annotation on top of your test class and pass SpringRunner.class.
#RunWith(SpringRunner.class)
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
The problem here is your service implementation. Using #Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.
Change your service implementation to:
#Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
This constructor will be called by spring. Then change your test to:
public class MyTest {
#Mock
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
Note I have replaced #MockBean to #Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with #SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.
PD: This test will not work if you don't provide a mock to myRepository.findAll(). Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0).
I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.
#RunWith(SpringRunner.class)
public class MyTest {
#Autowired
MyRepository myRepository;
#Autowired
MyService myService
#Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
This should work

Why is the constructor method being called before setup

Here is my class under test:
KafkaProcessorApplication
#EnableBinding(Processor.class)
#EnableConfigurationProperties(KafkaProperties.class)
public class KafkaProcessorApplication {
#Autowired
private Processor processor;
#Autowired
private KafkaProperties kafkaProperties;
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
KafkaProcessorApplication(SenderConfig senderConfig) {
this.kafkaTemplate = senderConfig.kafkaTemplate();
}
Here, SenderConfig is a just a simple config class with the method kafkaTemplate() creating a new instance of KafkaTemplate.
SenderConfig
#Configuration
public class SenderConfig {
#Autowired
KafkaProperties kafkaProperties;
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(new HashMap());
}
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(ProducerFactory()));
}
}
Here is the test class:
KafkaTestClass
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = {SenderConfig.class, KafkaProcessorApplication.class})
#TestPropertySource(locations = "classpath:test-resources.properties")
#RunWith(SpringRunner.class)
public class KafkaProcessorApplicationTest {
#Autowired
private Processor processor;
#Mock
private SenderConfig senderConfig;
#Mock
private KafkaProperties kafkaProperties = new KafkaProperties();
#Mock private KafkaTemplate mockKafka;
#Autowired
#InjectMocks
private KafkaProcessorApplication app;
#Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn("ServerConfig").when(kafkaProperties).getServersConfig();
when(senderConfig.kafkaTemplate()).thenReturn(kafkaTemplate);
}
I want to mock kafkaTemplate. But, its instantiation is in constructor which is being executed even before the #Before is executed, where the logic of mocking the method is written.
Just curious why is the constructor being executed first, and also, how can I mock the method if this is the case?
What could be the approaches of mocking the kafkaTemplate, without using Powermock and without modifying the class under test as I can not change it?
When you use #SpringBootTest, the Spring dependency tree is resolved before the #Before method has a chance to execute. This includes constructing the KafkaProcessorApplication bean and its dependencies. This is why the constructor runs before #Before.
What you want is Spring's #MockBean to create and inject a mock bean in the application context.
This question has a great write-up of how you can use this: Difference between #Mock, #MockBean and Mockito.mock()
update
Now I see. The problem is that the KafkaProcessorApplication accesses the mock in its constructor before you can configure it.
This can be solved with a separate test Spring configuration that will return a configured SenderConfig mock bean as described here: Testing spring bean with post construct

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