Actually, there were zero interactions with this mock. Embedded Kafka Spring test - spring

I'm trying to see if a method from Service class is invoked when the consumer consumes a message from Kafka topic but i'm getting the error that there is no zero interactions with the Mock. When the test is running it consumes the message and I can see on terminal that the service method is actually invoked (i tried with prints), but it is not passing the test.
My Consumer class:
#Component
public class Consumer {
#Autowired
private Service service;
#KafkaListener(topics = "topic")
public void consume(String message) {
service.add();
}
}
The test:
#SpringBootTest
#RunWith(MockitoJUnitRunner.class)
#DirtiesContext
#EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class ConsumerTest {
#Mock( lenient = true)
private Service service;
#Autowired
private KafkaTemplate<String, String> kafkaTemplate;
#InjectMocks
private Consumer consumer;
#Test
public void givenEmbeddedKafkaBroker_whenExistsTemperatureMessageInTopic_thenMessageReceivedByConsumerAndServiceInvoked()
throws Exception {
String message = "Hello";
kafkaTemplate.send("topic", message);
Mockito.verify(service, times(1)).add();
}
}

TLDR: Don't use #Mock with #SpringBootTest. Use #MockBean instead.
The components you created in your test don't take part in the message processing:
consumer
service
This stems from the fact that you used #SpringBootTest annotation, which brings up entire application context. This means that Spring creates all services itself, and happily ignores the ones created in the test.
To replace a bean from the test, use #MockBean
To inject a bean created by Spring to your test, use #Autowired

Related

Spring Boot JUnit tests fail with Status expected:<200> but was:<404>

For some time I've been struggling to make JUnit tests for my rest controller. For some reason, every time I try to run them I get the error Status expected:<200> but was:<404>. Here is my controller:
#RestController
#RequestMapping("/travels")
#RequiredArgsConstructor
public class TravelController {
private final TravelService travelService;
private final TravelOutputDtoMapper travelOutputDtoMapper;
#GetMapping
public List<TravelOutputDto> getAll() {
List<Travel> travels = travelService.getAll();
return travels.stream()
.map(travelOutputDtoMapper::travelToTravelOutputDto)
.collect(Collectors.toList());
}
}
And here is my test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = TravelController.class)
#ContextConfiguration(classes = {
TravelOutputDtoMapper.class,
TravelOutputDtoMapperImpl.class
})
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#Test
void testGetAll() throws Exception {
List<Travel> travels = mockTravelList();
Mockito.when(travelService.getAll()).thenReturn(travels);
mockMvc.perform(get("/travels"))
.andExpect(status().isOk());
}
private List<Travel> mockTravelList() {
// Dummy travel list
}
}
I think the reason is connected with TravelOutputDtoMapper as if I remove it from the controller and don't try to inject it the tests are passing, but I cannot find any information why it is doing it. The autowired mapper has an instance and works just fine.
Here is the Mapper:
#Mapper(componentModel = "spring")
public interface TravelOutputDtoMapper {
#Mapping(target = "from", source = "entity.from.code")
#Mapping(target = "to", source = "entity.to.code")
TravelOutputDto travelToTravelOutputDto(Travel entity);
}
The #ContextConfiguration annotation is used for a different purpose:
#ContextConfiguration defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.
Using Spring Boot and #WebMvcTest there's no need to manually specify how to load the context. That's done for you in the background.
If you'd use this annotation, you'd specify your main Spring Boot class here (your entry-point class with the #SpringBootApplication annotation).
From what I can see in your test and your question is that you want to provide an actual bean for the TravelOutputDtoMapper, but mock the TravelService.
In this case, you can use #TestConfiguration to add further beans to your sliced Spring TestContext:
// #ExtendWith(SpringExtension.class) can be removed. This extension is already registered with #WebMvcTest
#WebMvcTest(controllers = TravelController.class)
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#TestConfiguration
static class TestConfig {
#Bean
public TravelOutputDtoMapper travelOutputDtoMapper() {
return new TravelOutputDtoMapper(); // I assume your mapper has no collaborators
}
}
// ... your MockMvc tests
}

Why DirtiesContext is needed on other test classes to mock bean dependency for class with JMS Listener

Context
A Spring Boot application with a Rest endpoint and a JMS AMQ Listener
Test behaviour observed
The tests classes run fine without needing DirtiesContext individually but when the entire suite of test classes are run the following behaviours are observed -
Mocking of a bean dependency for the JMS Consumer test requires the earlier test classes to have a DirtiesContext annotation.
Mocking of bean dependency for RestControllers seem to work differently than a JMS Listener i.e don't need DirtiesContext on the earlier test classes
I've created a simple Spring application to reproduce the Spring context behaviour I need help understanding - https://github.com/ajaydivakaran/spring-dirties-context
The reason this happens is due to the fact that without #DirtiesContext Spring will remain the context for reuse for other tests that share the same setup (read more on Context Caching in the Spring documentation). This is not ideal for your setup as you have a messaging listener, because now multiple Spring Contexts can remain active and steal the message you put into the queue using the JmsTemplate.
Using #DirtiesContext ensures to stop the application context, hence this context is not alive afterward and can't consume a message:
from #DirtiesContext:
Test annotation which indicates that the {#link org.springframework.context.ApplicationContext ApplicationContext} *
associated with a test is dirty and should therefore be
closed and removed from the context cache.
For performance reasons, I would try to not make use of #DirtiesContext too often and rather ensure that the JMS destination is unique for each context you launch during testing. You can achieve this by outsourcing the destination value to a config file (application.properties) and randomly populate this value e.g. using a ContextInitializer.
A first (simple) implementation could look like the following:
#AllArgsConstructor
#Service
public class Consumer {
private EnergeticGreeter greeter;
private MessageRepository repository;
private ApplicationContext applicationContext;
#JmsListener(destination = "${consumer.destination}")
public void consume(
#Header(name = JmsHeaders.MESSAGE_ID, required = false) String messageId,
TextMessage textMessage) {
System.out.println("--- Consumed by context: " + applicationContext.toString());
if ("Ahem hello!!".equals(greeter.welcome().getContent())) {
repository.save();
}
}
}
the corresponding test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = DestinationValueInitializer.class)
public class JMSConsumerIntegrationTest {
#Autowired
private JmsTemplate jmsTemplate;
#Value("${consumer.destination}")
private String destination;
#Autowired
private ApplicationContext applicationContext;
#MockBean
private EnergeticGreeter greeter;
#MockBean
private MessageRepository repository;
//Todo - To get all tests in this project to pass when entire test suite is run look at Todos added.
#Test
public void shouldInvokeRepositoryWhenGreetedWithASpecificMessage() {
when(greeter.welcome()).thenReturn(new Message("Ahem hello!!"));
System.out.println("--- Send from context: " + applicationContext.toString());
jmsTemplate.send(destination, session -> session.createTextMessage("hello world"));
Awaitility.await().atMost(10, TimeUnit.SECONDS).untilAsserted(
() -> verify(repository, times(1)).save()
);
}
}
and the context initializer:
public class DestinationValueInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("consumer.destination=" + UUID.randomUUID().toString()).applyTo(applicationContext);
}
}
I've provided a small PR for your project where you can see this in the logs, that a different application context is consuming your message and hence you can't verify that the repository was called on the application context you write your test in.

Cannot inject #Service in Unit Test in SpringBoot project

i have a #Service that I am trying to mock in an Unit Test but i get a null value so far. In the application class I specify what are the scanBasePackages. Do I have to do this in a different way? Thanks.
This is my service class that implements an interface:
#Service
public class DeviceService implements DeviceServiceDao {
private List<Device> devices;
#Override
public List<Device> getDevices(long homeId) {
return devices;
}
}
This is my unit test.
public class SmartHomeControllerTest {
private RestTemplate restTemplate = new RestTemplate();
private static final String BASE_URL = “..”;
#Mock
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
Device activeDevice = new DeviceBuilder()
.getActiveDevice(true)
.getName("Alexa")
.getDeviceId(1)
.getHomeId(1)
.build();
Device inativeDevice = new DeviceBuilder()
.getInactiveDevice(false)
.getName("Heater")
.getDeviceId(2)
.getHomeId(1)
.build();
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(BASE_URL + "/1/devices");
List response = restTemplate.getForObject(builder.toUriString(), List.class);
verify(deviceService, times(1)).getDevices(1);
verifyNoMoreInteractions(deviceService);
}
You have to use a Spring test runner if you want to load and use a Spring context during tests execution.
You don't specify any runner, so it uses by default the runner of your test API. Here is probably JUnit or TestNG (the runner using depends on the #Test annotation specified).
Besides, according to the logic of your test, you want to invoke the "real"
REST service :
List response = restTemplate.getForObject(builder.toUriString(),
List.class);
To achieve it, you should load the Spring context and load the Spring Boot container by annotating the test with #SpringBootTest.
If you use a Spring Boot context, to mock the dependency in the Spring context, you must not use #Mock from Mockito but #MockBean from Spring Boot.
To understand the difference between the two, you may refer to this question.
Note that if you are using the #SpringBootTest annotation, a TestRestTemplate is automatically available and can be autowired into your test.
But beware, this is fault tolerant. It may be suitable or not according to your tests.
So your code could look like :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SmartHomeControllerTest {
private static final String BASE_URL = “..”;
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
...
}
As a side note, avoid using raw type as List but favor generic type.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = NotificationApplication.class)
public class EmailClientImplTest {
...
}
And also add the needed properties/configs in
/src/test/resources/application.yml
Good luck!
I figured it out, I am using Mockito and used that to annotate my test class. This allowed me to get a mock of the service class that i am trying to use.
#RunWith(MockitoJUnitRunner.class)
public class SmartHomeControllerTest {..
#Mock
private DeviceService deviceService;
}
Try with #InjectMock instead of #Mock
You should run your test with spring boot runner

GridFsTemplate NullPointerException in Service Unit Test Class (Tech Stack: Spring Data / Spring Boot / Micro Service / Mongodb )

I am developing a spring boot app.
The service method uploads a PDF into a mongodb repo using GridFsTemplate which is autowired in the service.
This file upload service method works as expected via postman rest client.
But, When I tried running a unit test; calling the same service method, the SpringData GridFsTemplate is not initialised (In MongoDB, you can use GridFS to store binary files). This results in the org.springframework.data.mongodb.gridfs.GridFsTemplate.store(...) throwing a NullPointerException.
Please, can you help, I have been stuck in this for a days.
Below is my service implementation:
#Service
public final class UploadServiceImpl implements UploadService {
#Autowired
private SequenceRepository sequenceDao;
#Autowired (required = true)
private GridFsTemplate gridFsTemplate;
#Override
public Long uploadFile(Invoice uploadedInvoice) {
ByteArrayInputStream byteArrayInputStream = null;
if (checkContentType(invoiceInfo.getContentType())) {
invoiceInfo.setPaymentID(sequenceDao.getNextSequenceId(INVOICE_UPLOAD_SEQ_KEY));
byteArrayInputStream = new ByteArrayInputStream(uploadedInvoice.getFileContent());
//Error thrown is java.lang.NullPointerException: null, where gridFsTemplate is null and basically autowire does not work when test is run.
GridFSFile gridFSUploadedFile= gridFsTemplate.store(byteArrayInputStream, invoiceInfo.getFileName(), invoiceInfo.getContentType(), invoiceInfo);
return 1l;
} else {
return 2l;
}
}
### Below is my Unit Test class for the service
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration
public class UploadServiceTest {
#Mock
private SequenceRepository sequenceRepositoryMock;
#Autowired
private GridFsTemplate gridFsTemplateMock;
#Mock
private Invoice invoiceMock;
#InjectMocks
private static UploadService uploadService = new UploadServiceImpl();
DBObject fileMetaData = null;
DB db = null;
Jongo jongo = null;
#Before
public void setUp() throws Exception {
db = new Fongo("Test").getDB("Database");
jongo = new Jongo(db);
}
#Test
public void testUploadFile() {
//test 1
Long mockPaymentNo = new Long(1);
Mockito.when(sequenceRepositoryMock.getNextSequenceId(INVOICE_SEQUENCE)).thenReturn(mockPaymentNo);
assertEquals(mockPaymentNo, (Long) sequenceRepositoryMock.getNextSequenceId(INVOICE_SEQUENCE));
//test 2
Invoice dummyInvoice = getDummyInvoice();
InvoiceInfo dummyInvoiceInfo = dummyInvoice.getInvoiceInfo();
MongoCollection invoicesCollection = jongo.getCollection("invoices");
assertNotNull(invoicesCollection.save(dummyInvoiceInfo));
assertEquals(1, invoicesCollection.save(dummyInvoiceInfo).getN());
System.out.println("TEST 2 >>>>>>>>>>>>>>>>>> "+ uploadService);
//test 3 : The following line is the cause of the exception, the service method is called but the GridFsTemplate is not initialized when the test is run. But it works when the endpoint is invoked via postman
uploadService.uploadFile(dummyInvoice);
System.out.println("TEST 3 >>>>>>>>>>>>>>>>>> ");
}
}
The problem is because you use #InjectMocks to autowire your UploadService.
And UploadService autowire two other beans SequenceRepository and GridFsTemplate.
If you’re doing TDD or not (and we are able to change the test first)
– clients of this code don’t know about an additional dependency,
because it’s completely hidden
The Javadoc states:
Mockito will try to inject mocks only either by constructor injection,
setter injection, or property injection in order and as described
below. If any of the following strategy fail, then Mockito won’t
report failure; i.e. you will have to provide dependencies yourself.
The solution is to use UploadServiceImpl constructor to autowire beans:
#Service
public final class UploadServiceImpl implements UploadService {
private final SequenceRepository sequenceDao;
private final GridFsTemplate gridFsTemplate;
private final PlannerClient plannerClient;
#Autowired
public PlannerServiceImpl(PlannerClient plannerClient, GridFsTemplate gridFsTemplate, SequenceRepository sequenceDao) {
this.plannerClient = plannerClient;
}
...
}
When there are more dependencies needed, they’re clearly in sight because they are initialized in constructor
More detailed:
https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/

Unit Testing a class with ServiceLocatorFactoryBean Autowired

I have a Interface which is registered as part of ServiceLocatorFactoryBean. The main purpose of this Interface is to act as a factory.
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
I have "autowired" this Interface in various classes, that I want to test now with Mockito.
The issue is Mockito doesn't support interfaces. How can inject a mock of this interface in the class I am testing.
The only alternative I see is to run the test using SpringJunitRunner and providing an Application Context which has the bean configurations. But this is too verbose.
I take it you'd like to spy on the implementation that Spring generated for your interface?! That's close to impossible to achieve with what you have so far... However there are at least the following alternatives below.
Suppose we have the following setup:
public interface MyService {
String doIt();
}
#Component
public static class ServiceConsumer {
#Autowired
private MyService service;
public String execute() {
return service.doIt();
}
}
0) Later edit: while roaming around, I found that it may be possible to spy and even replace an autowired field with a mock, and fairly easy too, using Springockito-annotations.
#RunWith(SpringJUnit4ClassRunner.class)
#ComponentScan
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class, classes = {SpringockitoConsumerTest.class})
public class SpringockitoConsumerTest {
#WrapWithSpy(beanName = "myService")
#Autowired
private MyService mockService;
#Autowired
private ServiceConsumer consumer;
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(mockService).doIt();
}
}
If Springockito-annotations is out of the question, please see the 2 original suggestions below
1) You could just create your mock of the interface and auto-inject it Mockito in your bean. This is the simplest solution (I could think of at the time of writing) but it does not ensure that the #Autowired annotation was not forgotten in the consumer (perhaps a dedicated test could be added?!):
public class AutoInjectMocksConsumerTest {
#Mock
private MyService serviceMock;
#InjectMocks
private ServiceConsumer consumer = new ServiceConsumer();
#Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
when(serviceMock.doIt()).thenReturn("allDone");
}
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(serviceMock).doIt();
}
}
2) Alternatively as you also said, you could run it with the SpringJunitRunner making a minimum of effort to define and instantiate the necessary Spring context while also providing your own service mock. Albeit people may complain this solution is not that clean, I find it sufficiently elegant and it also validates that the #Autowired annotation was not forgotten in the consumer implementation.
#RunWith(SpringJUnit4ClassRunner.class)
#Configuration
#ComponentScan
#ContextConfiguration(classes = {SpringAutowiringConsumerTest.class})
public class SpringAutowiringConsumerTest {
#Autowired
private MyService mockService;
#Autowired
private ServiceConsumer consumer;
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(mockService).doIt();
}
#Bean
public MyService mockService() {
MyService serviceMock = mock(MyService.class);
when(serviceMock.doIt()).thenReturn("allDone");
return serviceMock;
}
}

Resources