Test #EventListener with custom events Spring Kotlin - spring

I have created a set of custom events for my application
sealed class myEvent(open val id: Int) {
data class myBigEvent(override val id : Int) : myEvent(id)
data class myIntermediateEvent(override val id: Int): myEvent(id)
}
I have a service that has a method for listening my custom events
#Service
class MailService(
private val otherService: OtherService
) {
#EventListener(myEvent.myBigEvent::class, myEvent.myIntermediateEvent::class)
#Async
fun handleProcessEvent(event: myEvent) {
if (event.id != 10 && otherService.hasCompleted) {
sendMail(event)
}
}
The interesting point is that IntellIj annotates with the eventlistener icon next to the method defintion. When clicking on it I get redirected to all my invocations of publisher.publishEvent(myBigEvent) or publisher.publishEvent(myIntermediateEvent)
The issue is when I try to test it. I have the following setup
#TestExecutionListeners
class myEventTest {
#Autowired
private lateinit var publisher: ApplicationEventPublisher
#Mock
private lateinit var mailService: MailService
#BeforeClass
fun start() {
publisher = ApplicationEventPublisher {}
MockitoAnnotations.initMocks(this)
}
#Test
fun `should receive cmlaProcessEvent published event`() {
val cmlaProcessEvent = JobProcessEvent.CmlaProcessSimulationProcessEvent(mlaSimulationRun)
this.publisher.publishEvent(cmlaProcessEvent)
verify(mailService, times(1)).handleProcessEvent(cmlaProcessEvent)
}
}
I get 'Wanted but not invoked' ... 'Actually, there were zero interactions with this mock.'
I think my issue is with the ApplicationEventPublisher that is not sending the events to my MailService under this test context ?

Remove #TestExecutionListeners, since the default listeners should suffice.
If you're using Spring Boot, you should use #MockBean instead of #Mock.
In any case, you have to actually instruct JUnit to use Spring's testing support.
I assume that you are using JUnit 4, since I see #BeforeClass in the example. So I'm basing the following on that assumption (and using Java syntax instead of Kotlin).
You can instruct JUnit 4 to use Spring's testing support via #RunWith(SpringRunner.class). Note, however, that you'll also need to specify where your ApplicationContext configuration is.
For Spring Framework, you can do that with #ContextConfiguration. For Spring Boot, you'll likely want to use #SpringBootTest. Consult the documentation for testing support in Spring Framework and Spring Boot for details.
As a side note, you can also test ApplicationEvent publication without using a mock by using Spring Framework's built-in testing support for application events.

Related

NullPointerException while I'm trying to unit test Spring Boot Application

When I try to test my CRUD operations in my Spring Boot Applications, I get NullPointerException saying that this.Repository is null. What can I do to resolve this issue? Am I missing something?
My test class:
#RunWith(MockitoJUnitRunner.class)
class AppointmentServiceTest {
#Mock
private AppointmentRepository appointmentRepository;
#InjectMocks
private AppointmentService appointmentService;
#Test
void shouldGetAllAppointments() {
List<Appointment> appointments = new ArrayList<>();
appointments.add(new Appointment());
given(appointmentRepository.findAll()).willReturn(appointments);
List<Appointment> expectedAppointments = appointmentService.getAllAppointments();
assertEquals(expectedAppointments, appointments);
verify(appointmentRepository.findAll());
}
}
I am getting NullPointerException:
java.lang.NullPointerException: Cannot invoke "com.app.hospitalmanagementsystem.repository.AppointmentRepository.findAll()" because "this.appointmentRepository" is null
Since the spring boot is tagged here, the chances that you're using spring boot 2.x (1.x is outdated these days)
But if so, you should be running JUnit 5 tests (spring boot 2.x works with Junit 5)
So instead of #RunWith annotation, use #ExtendsWith
Then place the breakpoint in the test and make sure that mockito extension has actually worked and the mock is created.
Now as for the given - I can't say for sure, I haven't used this syntax (BDD Mockito), but in a "clean mockito" it should be Mockito.when(..).thenReturn
All-in-all try this code:
#ExtendsWith(MockitoExtension.class)
class AppointmentServiceTest {
#Mock
private AppointmentRepository appointmentRepository;
#InjectMocks
private AppointmentService appointmentService;
#Test
void shouldGetAllAppointments() {
List<Appointment> appointments = new ArrayList<>();
appointments.add(new Appointment());
Mockito.when(appointmentRepository.findAll()).thenReturn(appointments);
List<Appointment> expectedAppointments = appointmentService.getAllAppointments();
assertEquals(expectedAppointments, appointments);
verify(appointmentRepository.findAll());
}
}

MeterRegistry counter test case failing

I have implemented Micrometer Prometheus counter in my service by injecting MeterRegistry and incrementing the count as shown below, and I have written a test case as well, but when I am running the test case, I am getting:
"java.lang.NullPointerException: Cannot invoke
"io.micrometer.core.instrument.MeterRegistry.counter(String,
String[])" because "this.meterRegistry" is null".
Service file:
#Autowired
private MeterRegistry meterRegistry;
public void counterIncrement() {
meterRegistry.counter("test_count").increment();
}
Test case file:
#MockBean
private MeterRegistry registry;
#Test
void testCounter() {
// invoking counterIncrement();
}
How do you create your class under test?
Since the registry is never instantiated, something seems up with how you setup your test.
Check that you are using the #MockBean in the correct way. This will replace the bean in the application context and if you do not spin up a spring context in your test, it will not work. See this post for more info.
A different approach would be to use #Mock and inject the registry in the constructor, example:
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MeterRegistry registry;
private MyService myService;
#BeforeEach
void setup() {
myService = new MyService(registry);
}
#Test
void testCounter() {
var counter = mock(Counter.class);
given(registry.counter(any(String.class))).willReturn(counter);
myService.counterIncrement();
}
You can test metrics without Mockito using SimpleMeterRegistry
#Test
void testCounter() {
var meterRegistry = new SimpleMeterRegistry();
Metrics.addRegistry(meterRegistry);
// invoke counterIncrement();
var actual = meterRegistry.counter("test_count").count();
assertEquals(1.0d, actual);
}
Depending on which junit version you are using you need to add the annotation to your test class. Junit 5: #ExtendWith(MockitoExtension.class) or for Junit 4: #RunWith(MockitoJUnitRunner.class)
Depending on the test and the service there are several ways to deal with the missing MeterRegistry.
If you use a spring context in your test, try to use a test configuration to create the MeterRegistry bean.
If your test uses some Mock framework, you could mock the MeterRegistry as suggested by by #Hans-Christian.
If you simply make the member meterRegistry non-private. You could set it to a SimpleMeterRegistry in some setup method, anotated with #BeforeEach as suggested by #checketts in the comments.
If mocking the meter registry gets complicated, you could easily build and use some factory that provides the registry and mock this factory. A very easy factory will do, e.g. a spring #Component with an autowired MeterRegistry and some public getter for the factory.
You could use the factory method pattern as described in wikipedia to get the MeterRegistry, overwrite the factory method in a subclass of your service and use this subclass in the test. (Note that the gang of four did use a static factory method, you'll need a non-static method.)
I favour solution 3 but would use solution 1 whenever appropriate. I've added solutions 4 and 5 just because there might be some additional reasons and special cases that make these solutions a good choice. If so, I prefer 4 over 5.

Kotlin, Spring book, Mockito, #InjectMocks, Using different mocks than the ones created

I am trying to test a class like
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class)
class TotalCalculatorTests {
#Mock
lateinit var jdbcRepo: JDBCRepo
#Mock
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#InjectMocks
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
I get an error Actually, there were zero interactions with this mock. but if I uncomment the // val calculator = TotalCalculator(userCache,jdbcRepo) it works. I assumed mockito would be using the mocks from the test class but that appears to not be true. How can I get the instance of JDBCRepo being used by the #InjectMocks?
It looks like you would like to run the full-fledged spring boot test with all the beans but in the application context you would like to "mock" some real beans and provide your own (mock-y) implementation.
If so, the usage of #Mock is wrong here. #Mock has nothing to do with spring, its a purely mockito's thing. It indeed can create a mock for you but it won't "substitute" the real implemenation with this mock implementation in the spring boot's application context.
For that purpose use #MockBean annotation instead. This is something from the spring "universe" that indeed creates a mockito driven mock under the hood, but substitutes the regular bean in the application context (or even just adds this mock implementation to the application context if the real bean doesn't even exist).
Another thing to consider is how do you get the TotalCalculator bean (although you don't directly ask this in the question).
The TotalCalculator by itself is probably a spring been that spring boot creates for you, so if you want to run a "full fledged" test you should take the instance of this bean from the application context, rather than creating an instance by yourself. Use annotation #Autowired for that purpose:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class) // you don't need this
class TotalCalculatorTests {
#MockBean
lateinit var jdbcRepo: JDBCRepo
#MockBean
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#Autowired // injected by the spring test infrastructure
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
Now, all this is correct if your purpose to indeed running the full microservice of spring boot and make an "integration" testing
If alternatively you just want to check the code of your calculator, this might be an overkill, you can end up using mockito and plain old unit testing. In this case however you don't need to even start the spring (read create an application context) during the test, and of course there is no need to use #SpringBootTest / #MockBean/#Autowired.
So it pretty much depends on what kind of test is really required here

#WebMvcTest vs #ContextConfiguration when testing WebSocket controllers

I'm currently in the process of writing tests for my controllers in a Spring Boot project that uses WebSockets.
As information on the subject is hard to come by, my only lead is this example recommended by the docs.
Allow me to attempt to explain my forays so far, trying to understand and get my test environment set up. I'm following the context-based approach, and I'm torn between #WebMvcTest and #ContextConfiguration (which the example uses).
My motivation behind using #WebMvcTest at all was this line from the Spring Boot docs:
When testing Spring Boot applications, this [using #ContextConfiguration(classes=…​) in order to specify which Spring #Configuration to load, or using nested #Configuration classes within your test] is often not required. Spring Boot’s #*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.
#WebMvcTest thus seems particularly suited to the task as it focuses only on the web layer by limiting the set of scanned beans to only those necessary (e.g. #controller) rather than spinning up a complete ApplicationContext.
The code example I'm following uses field injection to initialize channel interceptors to capture messages sent through them.
#Autowired private AbstractSubscribableChannel clientInboundChannel;
#Autowired private AbstractSubscribableChannel clientOutboundChannel;
#Autowired private AbstractSubscribableChannel brokerChannel;
As far as I can tell, these fields are what makes the presence of the TestConfig class (see code block at the end for full class definition) in the example necessary, since without it, I get an error saying no beans qualify as autowire candidates. I believe these two fields in TestConfig are the key:
#Autowired
private List<SubscribableChannel> channels;
#Autowired
private List<MessageHandler> handlers;
However, without #ContextConfiguration(classes = [WebSocketConfig::class]) (WebSocketConfig being my own WebSocket configuration file), these two fields are always null resulting in errors.
So far, this would mean that #ContextConfiguration(classes = [WebSocketConfig::class]) in combination with the presence of TestConfig are needed.
The interesting thing is that without #WebMvcTest, clientInboundChannel, clientOutboundChannel, and brokerChannel are never actually initialized. So what this leaves me with is that I need both #WebMvcTest and #ContextConfiguration, which somehow seems odd.
And with the last update to the example repo being more than two years old, I can't shake the feeling that it may be somewhat outdated.
This is what my test class (Kotlin) currently looks like. I've omitted the createRoom test case for brevity:
#WebMvcTest(controllers = [RoomController::class])
#ContextConfiguration(classes = [WebSocketConfig::class, RoomControllerTests.TestConfig::class])
class RoomControllerTests {
#Autowired
private lateinit var clientInboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var clientOutboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var brokerChannel: AbstractSubscribableChannel
private lateinit var clientOutboundChannelInterceptor: TestChannelInterceptor
private lateinit var brokerChannelInterceptor: TestChannelInterceptor
private lateinit var sessionId: String
#BeforeEach
fun setUp() {
brokerChannelInterceptor = TestChannelInterceptor()
clientOutboundChannelInterceptor = TestChannelInterceptor()
brokerChannel.addInterceptor(brokerChannelInterceptor)
clientOutboundChannel.addInterceptor(clientOutboundChannelInterceptor)
}
#Test
fun createRoom() {
// test room creation
// ...
}
#Configuration
internal class TestConfig : ApplicationListener<ContextRefreshedEvent?> {
#Autowired
private val channels: List<SubscribableChannel>? = null
#Autowired
private val handlers: List<MessageHandler>? = null
override fun onApplicationEvent(event: ContextRefreshedEvent) {
for (handler in handlers!!) {
if (handler is SimpAnnotationMethodMessageHandler) {
continue
}
for (channel in channels!!) {
channel.unsubscribe(handler)
}
}
}
}
}
The answer posted here was my first clue towards starting to understand how #WebMvcTest and #ContextConfiguration are meant to be used.
It would appear the line that is really necessary is #ContextConfiguration(classes = [WebSocketConfig::class]), since without it, the necessary configuration (my own WebSocketConfig, in this case) can't be found.
While #WebMvcTest (and test slices in general) does automatically search for the primary configuration, the configuration defined by the main application class is the one that's found since that class is the one annotated with #SpringBootApplication.
What's more, test slices such as #WebMvcTest flat out exclude #configuration classes from scanning, meaning they won't be included in the application context loaded by the test slice.
Supporting this is the fact that the test works if #WebMvcTest is replaced with #SpringBootTest — even without #ContextConfiguration(classes = [WebSocketConfig::class]) — meaning that WebSocketConfig was loaded.
To summarize, as I see it, there are 3 ways to include a non-primary configuration in the application context so that it's detected in integration tests:
Annotate the test class with #SpringBootTest (and optionally #ContextConfiguration to restrict the number of loaded beans to only those needed)
Annotate the test class with a test slice (e.g. #WebMvcTest) in conjunction with #ContextConfiguration
Annotate the test class with a test slice (e.g. #WebMvcTest) and define the configuration in the main class (i.e. the one annotated with #SpringBootApplication)

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.

Resources