Create #MockBean with qualifier by annotating class? - java

In my Spring Boot test I'm using 2 mock beans with different qualifiers:
#RunWith(SpringRunner.class)
#SpringBootTest
class HohoTest {
#MockBean #Qualifier("haha") IHaha ahaha;
#MockBean #Qualifier("hoho") IHaha ohoho;
}
Since I'm not using these beans explicitly, I would rather move them away from the class body, as the #MockBean annotation is now repeatable:
#RunWith(SpringRunner.class)
#SpringBootTest
#MockBean(IHaha.class)
#MockBean(IHaha.class)
class HohoTest {}
However, I need to pass in a qualifier as well, since they have the same type. Any idea on how I can achieve that?

Because using annotation #Qualifier means choose bean by name, so you can set up a name for a mock with code like this:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {JsonMapperConfig.class})
public class IntegrationFlowTest {
#MockBean(name = "s3MessageRepository")
private S3Repository s3MessageRepository;
// etc

If it is okay to move the mock definition completely out of the test class, you could also create the mocks in a separate #Configuration class:
#Configuration
public class MockConfiguration
{
#Bean #Qualifier("haha")
public IHaha ahaha() {
return Mockito.mock(IHaha.class);
}
#Bean #Qualifier("hoho")
public IHaha ohoho() {
return Mockito.mock(IHaha.class);
}
}

When declaring #MockBean at the class level, there is currently no support for providing a qualifier.
If you would like to have such support, I suggest you request it in the Spring Boot issue tracker.
Otherwise, you will need to continue declaring #MockBean on fields alongside #Qualifier.

I had a similar requirement of injecting mocked service beans with #Order annotation. I also needed to verify the invocation count of service functions. Below is my implementation. It might help someone.
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceNameTest {
#Autowired private ServiceName serviceName;
// Important: Used to reset interaction count of our static
// bean objects before every test.
#Before
public void reset_mockito_interactions() {
Mockito.clearInvocations(MockServicesConfig.bean1);
Mockito.clearInvocations(MockServicesConfig.bean2);
}
#Configuration
public static class MockServicesConfig {
public static InterfaceName bean1;
public static InterfaceName bean2;
#Bean
#Order(1)
public InterfaceName bean1() {
bean1 = Mockito.mock(InterfaceName.class);
// Common when() stubbing
return bean1;
}
#Bean
#Order(2)
public InterfaceName vmpAdapter() {
bean2 = Mockito.mock(InterfaceName.class);
// Common when() stubbing
return bean2;
}
}
#Test
public void test_functionName_mock_invocation1() {
// Arrange --> Act --> Assert
// nullify other functions custom when() stub.
// updating this functions custom when() stub.
verify(MockServicesConfig.bean1, times(1)).functionName("");
}
#Test
public void test_functionName_mock_invocation2() {
// Arrange --> Act --> Assert
// nullify other functions custom when() stub.
// updating this functions custom when() stub.
verify(MockServicesConfig.bean1, times(1)).functionName("");
}
}

This should now work
#SpringBootTest(
classes = Some.class
)
#MockBean(name = BEAN_NAME, classes = TheBeanClass.class)
#MockBean(name = BEAN_NAME_2, classes = TheBeanClass.class)
class SomeTest {
private final Some some;
#Autowired
SomeTest(Some some) {
this.some = some;
}
}
Please note, if you need to use any of the mocked beans, you will have to put the #Qualifier in the constructor, for example
private final TheBeanClass theBeanclass;
private final Some some;
#Autowired
SomeTest(Some some, #Qualifier(BEAN_NAME) TheBeanClass theBeanClass) {
this.some = some;
this.theBeanClass = theBeanClass;
}

Related

Mockito: How to Mock objects in a Spy

The application runs in JEE environment.
I wish inject a Spy into a bean under test.
The Spy object has also some beans inside that should be injected. How can inject mocks of those beans into the Spy?
This is the usecase:
package testinject2;
import javax.inject.Inject;
public class ABean {
#Inject
BBean b;
public void print() {
System.out.println("Hi, I'm ABean");
b.print();
}
}
package testinject2;
import javax.inject.Inject;
public class BBean {
#Inject
CBean c;
public void print() {
System.out.println("Hi, I'm BBean");
c.print();
}
}
package testinject2;
public class CBean {
public void print() {
System.out.println("Hi, I'm CBean");
}
}
package testinject2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
public class ABeanTest {
#Spy
CBean c = new CBean();
#Spy
BBean b = new BBean();
#InjectMocks
ABean beanUnderTest;
#Test
public void test() {
beanUnderTest.print();
}
}
I'm expect to obtain
Hi, I'm ABean
Hi, I'm BBean
Hi, I'm CBean
But instead I have a null ponter exception because CBean is not injected into BBean.
Which is the correct way to Inject a Spy object into another Spy ?
You need to define to which object mocks should be injected via #InjectMocks annotation, but it does not work together with #Spy annotation. See mockito issue.
There is the simplest solution to use Mockito.spy instead of #Spy together with #InjectMocks:
#InjectMocks
BBean b = Mockito.spy(new BBean());
Full test code:
#RunWith(MockitoJUnitRunner.class)
public class ConfigTestObject {
#Spy
CBean c = new CBean();
#InjectMocks
BBean b = Mockito.spy(new BBean());
#InjectMocks
ABean beanUnderTest;
#Test
public void test() {
beanUnderTest.print();
//verify that mocks is working
verify(c, atLeast(1)).print();
verify(b, atLeast(1)).print();
}
}

NPE while running test in Spring application (JUnit 5, Mockito 3, Spring JPA repositories)

I'm running a basic Spring App with Mockito 3.1.0 and and Junit 5.5.2. I have a Service call that includes two Spring Data JPA repositories. These are passed into the constructor for DI (along with two others that are immaterial – I'm including them in case they could also, potentially, cause errors.) I see no issues with the service when the app runs.
When I run my test, I get a NPE for myService. Stepping through the stack trace, hasn't really shown me anything that relates to the error. I have also tried (following this Article: https://www.baeldung.com/mockito-junit-5-extension) updating my test class to look like this:
#ExtendWith(MockitoExtension.class)
#RunWith(JUnitPlatform.class) // This dependency doesn't seem to exist
public class MyServiceTest {
// ...
#BeforeEach
// not the JUnit4 #Before annotation.
// Interestingly, this gives me NPEs for the repositories, not the service.
public void setup(){
// ...
}
}
to no avail. What I suspect is happening is that something about my setup isn't properly wired up – either as dependencies or syntax for DI.
How do I debug this? What am I missing? Thanks in advance!
Service:
import org.springframework.stereotype.Service;
#Service
public class MyService {
private final Repository1 repository1;
private final Repository2 repository2;
private final Repository3 repository3;
private final Repository4 repository4;
public MyService(Repository1 repository1,
Repository2 repository2,
Repository3 repository3,
Repository4 repository4) {
this.repository1 = repository1;
this.repository2 = repository2;
this.repository3 = repository3;
this.repository4 = repository4;
}
public Boolean computeValue(String someInput) {
// does computations with repository1, repository2.
}
}
Test:
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {
#Mock
private Repository1 repository1;
#Mock
private Repository2 repository2;
#Mock
private Repository3 repository3;
#Mock
private Repository4 repository4;
#InjectMocks
private MyService myService;
#Before
public void setup {
when(repository1.findAll()).thenReturn(new ArrayList<>());
when(repository1.findAllByInput(anyString())).thenReturn(new ArrayList<>());
// Yes; I'm aware that this could also be a call to
// MockitoAnnotations.initMocks(this). I've tried it:
// it doesn't work. Also, I've intentionally not taken this
// approach due to reasons:
// - https://stackoverflow.com/questions/10806345/runwithmockitojunitrunner-class-vs-mockitoannotations-initmocksthis
}
#Test
void callMyService() {
assertTrue(myService.computeValue("123"));
}
}
Sample Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
// This is just an example, but this pattern is repeated against all
// repositories in the project.
#Repository
public interface Repository1 extends JpaRepository<Repository1, String> {
}
Edit:
I forgot to mention that I have other files in this project that are using exactly these annotations (#RunWith(MockitoJUnitRunner.class), #Mock, #InjectMocks, #Before) that are not failing.
I updated the files with the relevant imports, and added an example of RepositoryN.
I update the MyService class to better reflect the parameters.
For anybody else who encounters this in the future, we were able to fix this problem by changing one of the imports from:
import org.junit.jupiter.api.Test;
to
import org.junit.Test;
Edit:
This had to do with differing versions of JUnit. There's a good long-form explanation as to why here.
Seems like your object myService, is not instantiated. I would suggest not use #InjectMocks and directly create your object as your repositories are already instantiated.
MyService myService = new MyService(..., ..., ...)
I suppose you have to annotate your test class with #ExtendWith(SpringExtension.class) and not with MockitoExtension.class
More info here Junit 5 with Spring Boot: When to use #ExtendWith Spring or Mockito?

Getting NotAMockException on the below Test Case

I have tried to run the below test and am facing NotAMock Exception and not sure how to resolve it. I have been trying to read the concept that methods of class under test cannot be mocked but I am unable to come clear on the subject. If someone could explain me the Why on my own example, I am hopeful of understanding it better.
I tried various ways of changing #RunWith runners for Unit or Integration test setup or using #Spy instead of #Mock or not have #Autowired etc but either was facing dao Null Pointer or Not a Mock Exception variably.
Am I supposed to Use another class and inject the Listener in that class and mock the listener to achieve the functionality of being able to mock the methods and capture the arguments dynamically. Will this work because it is no more the class under test and therefore the methods could be mocked? If so, how is this realized. If not, what is the right way. My sense is moving the listener to another class will only extend my current set of issues of not being able to mock but does not resolve it. However, I am not sure what is the right outcome.
#Component
public class FileEventListener implements ApplicationListener<FileEvent> {
#Autowired private FetchFileDetailsDAO fileDao;//Dao is annotated with #Transactional
#Override
public void onApplicationEvent(FileEvent event) {
fileDao.getDetailsForFile(event.fileName())
}
}
-----------------------------------------------------------------------------------------
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
#SpringBootTest(classes = TestApp.class)
#RunWith(SpringRunner.class)
public class TestClass {
#Captor private ArgumentCaptor<Object> captor;
#Mock #Autowired private FetchFileDetailsDAO dao;
#InjectMocks #Autowired private FileEventListener listener;
#Before
public void setup() throws IOException {
MockitoAnnotations.initMocks(this);
}
#Test
#Transactional
#Rollback(true)
public void test() throws Exception {
FileEvent ev = new FileEvent();
...
listener.onApplicationEvent(ev);
verify(dao, times(1)).getDetailsForFile((String)captor.capture())
}
You are mixing things up here. There is an important difference between #Mock and #MockBean.
You use the first annotation if you want to write a unit test without any Spring Context support (speak #SpringBootTest, #DataJpaTest, etc.). For such tests, you can use #Mock and #InjectMocks.
As you are writing an integration test (you are starting the whole context with #SpringBootTest), you work with managed Spring beans inside your test. Hence you are not writing a unit test anymore.
If you want to replace a Spring bean with a mocked version of it inside your Spring Test Context, you have to use #MockBean:
#SpringBootTest(classes = TestApp.class)
#RunWith(SpringRunner.class)
#RunWith(MockitoJUnitRunner.class) // will do the Captor initialization for you
public class TestClass {
#Captor
private ArgumentCaptor<Object> captor;
#MockBean
private FetchFileDetailsDAO dao;
#Autowired
private FileEventListener listener;
#Test
#Transactional
#Rollback(true)
public void test() throws Exception {
FileEvent ev = new FileEvent();
// ...
listener.onApplicationEvent(ev);
verify(dao, times(1)).getDetailsForFile((String)captor.capture())
}
Starting the whole context however for this test is IMHO overkill. You are better off writing a good old unit test with just JUnit and Mockito.
In addition to this, also I would rethink what benefit your current tests adds to your project as it is literally duplicating the business logic. Maybe there is more code that is not present here.
You can find a more detailed summary for the difference between #Mock and #MockBean in this article.
I think the problem is the following line
#Mock #Autowired private FetchFileDetailsDAOImpl dao;
Try #Mock private FetchFileDetailsDAOImpl dao; instead

How do I make Autowired work inside a Configuration class?

I'm trying to autowire an attribute (myService) which is tagged as a #Service, inside a #Configuration class, but I get a NullPointer.
If instead, I autowire myService in non-configuration classes, I have no issues.
Here's the #Service I'm having issues autowiring:
package com.myapp.resources;
#Service
class MyService {
public List<String> getRoutingKeys() {
List<String> routingKeys;
//Do stuff
return routingKeys;
}
public String aMethod() {
return "hello";
}
}
Here's the #Configuration class where I can't autowire the Service
package com.myapp.messaging;
import com.myapp.resources;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
#Configuration
public class RabbitConfiguration {
private List<String> routingKeys = writeRoutingKeys();
#Autowired
private MyService myService;
private List<String> writeRoutingKeys() {
boolean test = myService == null;
System.out.println("is the service null? " + test); //output: true!!!
return myService.getRoutingKeys(); //here I get a NullPointer
}
//Methods with bean declarations for RabbitMQ
}
If it helps, here's my mainclass:
package com.myapp;
import com.myapp.resources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.List;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext appContext = SpringApplication.run(Application.class, args);
MyService myService = (MyService) appContext.getBean(MyService.class);
boolean test = myService == null;
System.out.println("is the service null? " + test); //output: false
//Do stuff
}
}
If it helps, here's a different class (a #RestController) where I'm able to autowire the service
package com.myapp.resources;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyController {
#Autowired
private MyService myService;
#GetMapping("/endpoint")
public String myRestMethod() {
boolean test = myService == null;
System.out.println("is the service null? " + test); //output: false
return myService.aMethod();
}
}
I've also tried adding the #ComponentScan in the Configuration class, but I still get a NullPointer
package com.myapp.messaging;
//list of imports...
#Configuration
#ComponentScan("com.myapp.demo")
public class RabbitConfiguration {
#Autowired
private MyService myService;
//...
}
Spring will only inject the dependencies after or when a bean is instantiated (Depending if constructor injection is used or not). However , you are now accessing the dependency MyService during the field initialisation which happens before initialising a bean .Hence , it cannot access MyService during field initialisation as it is not injected yet.
You can simply fix it by changing to use constructor injection and initialise routingKeys inside a constructor at the same time :
#Configuration
public class RabbitConfiguration {
private List<String> routingKeys ;
private MyService myService;
#Autowired
public RabbitConfiguration(MyService myService){
this.myService = myService
this.routingKeys = writeRoutingKeys();
}
private List<String> writeRoutingKeys() {
return myService.getRoutingKeys();
}
}
Or simply :
#Autowired
public RabbitConfiguration(MyService myService){
this.myService = myService
this.routingKeys = myService.getRoutingKeys();
}
I would suggest injecting the service through any #Bean creation method that needs it:
#Bean
public MyBean create(MyService myService)
and then pass the service into the writeRoutingKeys(MyService myService) method to process it accordingly.
Per documentation:
#Configuration classes are processed quite early during the
initialization of the context and forcing a dependency to be injected
this way may lead to unexpected early initialization. Whenever
possible, resort to parameter-based injection as in the example above.

Spring Boot can't find my autowired class (Autowired members must be defined in valid Spring bean)

I'm trying to use Spring's autowire annotation in my test class in order to inject an instance of a class.
package com.mycom.mycust.processing.tasks.references;
public class ReferenceIdentifierTest {
#Autowired
private FormsDB formsDB;
#PostConstruct
#Test
public void testCreateTopLevelReferencesFrom() throws Exception {
ReferenceIdentifier referenceIdentifier = new ReferenceIdentifier(formsDB);
}
}
This is the FormsDB class:
package com.mycom.mycust.mysql;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
#Component
public class FormsDB extends KeyedDBTable<Form> {
public FormsDB(ConnectionFactory factory) throws SQLException {
super(factory.from("former", new FormsObjectMapper()));
}
}
And here is the SpringBootApplication class:
package com.mycom.mycust.processing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan("com.mycom.mycust")
public class Processing implements CommandLineRunner {
// Code
}
When I run my test, formsDB is null. Since I've used the PostConstruct annotation on the test function I think that FormsDB could not be autowired due to the class not being found. There is also an IntelliJ warning on the Autowired annotation in test class: Autowired members must be defined in valid Spring bean (#Component|#Service...). But I have put the Component annotation above the FormsDB class and I've also put the path com.mycom.mycust in the ComponentScan annotation of the SpringBootApplication. So I can't see why it can't find the class.
What is wrong here?
Your test calls is missing some important annotations to make autowiring work:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ReferenceIdentifierTest {
#Autowired
private FormsDB formsDB;
#Test
public void testCreateTopLevelReferencesFrom() throws Exception {
ReferenceIdentifier referenceIdentifier = new ReferenceIdentifier(formsDB);
}
}
also you can remove #PostConstruct that does not make sense in a test.

Categories

Resources