I'm testing following function.
public boolean produceNumberOfPeople(NumberOfPeopleInPlaceDTO numberOfPeopleInPlaceDTO) {
final ProducerRecord<Integer, Integer> record = new ProducerRecord<>(
KafkaConfig.NUMBER_OF_PEOPLE_BY_PLACE_TOPIC,
numberOfPeopleInPlaceDTO.getId(),
numberOfPeopleInPlaceDTO.getNumberOfPeople());
try {
kafkaTemplate.send(record).get(2, TimeUnit.SECONDS);
return true;
}
catch (ExecutionException | TimeoutException | InterruptedException e) {
return false;
}
}
Here is test code.
@Test
public void produceNumberOfPeopleTest() throws InterruptedException, ExecutionException, TimeoutException {
NumberOfPeopleInPlaceDTO numberOfPeopleInPlaceDTO = NumberOfPeopleInPlaceDTO.builder()
.id(1)
.numberOfPeople(10)
.build();
Mockito.when(kafkaTemplate.send(Mockito.any(ProducerRecord.class)))
.thenReturn(listenableFuture);
Mockito.when(listenableFuture.get(2,TimeUnit.SECONDS))
.thenThrow(TimeoutException.class);
Assert.assertFalse(placeService.produceNumberOfPeople(numberOfPeopleInPlaceDTO));
}
And I defined following variables.
@Autowired
private PlaceService placeService;
@MockBean
private PlaceRepository placeRepository;
@MockBean
private KafkaTemplate<Integer, Integer> kafkaTemplate;
@MockBean
private ListenableFuture listenableFuture;
The problem is that kafkaTemplate.send(record).get(2,TimeUnit.SECONDS) doesn't throw exception. So test keep failing.
Please advice anything I 'm missing.
I will recommend to create failed ListenableFuture object with exception instead of Mock
SettableListenableFuture<SendResult<String, Object>> future = new SettableListenableFuture<>();
future.setException(new RuntimeException())
And then just return this in mock
Mockito.when(kafkaTemplate.send(Mockito.any(ProducerRecord.class))).thenReturn(listenableFuture);
So when the get method is called it throws ExecutionException
This method returns the value if it has been set via set(Object), throws an ExecutionException if an exception has been set via setException(Throwable), or throws a CancellationException if the future has been cancelled.
I am glad you solved by passing the instances to the constructor.
However, instead of creating a proper constructor, and instantiate the placeService in the test method itself, I would use another approach.
As best practice, it is recommendable to have specific setXXX methods to pass the instances, for example in your case, in PlaceService class you should have something like this:
public void setListenableFuture(ListenableFuture listenableFuture) {
this.listenableFuture = listenableFuture;
}
public void setKafkaTemplate(KafkaTemplate<Integer, Integer> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
Then, you can invoke them either in your test method (in your case produceNumberOfPeopleTest) or, even better in a specific setUp one, like this:
@Before
public void setUp() throws Exception {
placeService.setListenableFuture(listenableFuture);
placeService.setKafkaTemplate(kafkaTemplate);
}
In this way, you can leave Mock objects and placeService as members of your test class, so that JUnit and Spring Runner will have the responsibility of instantiating those objects and inject them in placeService, then you can just customize the mock behaviours each test methods you will write, according to your needs.
In my experience, I found this quite helpful, as each object involved does its proper job. Even in terms of test implementation and maintainability, you will not have to repeat the same code at each test method. For example, think about what could happen if at the certain point you have to change that constructor, in that case you will have to change all methods where you used it as well. Don't you think?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With