Search code examples
apache-kafkaintegration-testingspring-kafkajunit5

How to properly test kafkaTemplate.send() within a function in Junit5?


I'm learning how to write tests and especially tests that have a producer in it. I cannot post all the classes because it's HUGE (and not mine, I should just practice by changing the test to work with KafkaTemplate). I'm lost as to how a call like this should be tested.

I'm getting a NPE because of a producer.send("topic", JsonObject) that is in the function I'm testing. The functions is built like so:

@Autowired
private KafkaTemplate<String,EventDto> kafkaTemplate;

public EventDto sendEvent(Event event) {
       EventDto eventToSend = this.dtoMapper.mapToDto(event, SomeEvent.class);
       this.kafkaTemplate.send("topic",eventToSend);
       return eventToSend;
   }

in the unit test it's like this (irrelevant parts omitted):

@Test
void testSendEvent() {
      //omitted lines regarding second assert that works
      
      EventProducer producer = new EventProducer(something);
      EventDto dto = producer.sendEvent(Event.newBuilder().build());
      assertThat(dto).isNotNull();
      //there is a second assert here that passes, nothing to do with kafka
}

We have Mockito and I assume I need to mock the KafkaTemplate somehow. But I'm not quite getting how I can "direct" the sendEvent to use the KafkaTemplate within the producer.sendEvent() call?

Solution edit: I changed the @Autowired to injecting it with the constructor instead. Works well! Here is the full class and method now

@Service
public class EventProducer implements EventProducerInterface {

   private final DtoMapper dtoMapper;
   private KafkaTemplate<String,EventDto> kafkaTemplate;

   @Autowired
   public EventProducer (KafkaTemplate<String,EventDto> kafkaTemplate, IDtoMapper dtoMapper) {
          Assert.notNull(dtoMapper, "dtoMapper must not be null");
          this.dtoMapper = dtoMapper;
          this.kafkaTemplate=kafkaTemplate;
       }

   public EventDto sendEvent(Event event) {
       EventDto eventToSend = this.dtoMapper.mapToDto(event, EventDto.class);
       this.kafkaTemplate.send("output-topic",eventToSend);
       return eventToSend;
   }
}


Solution

  • You should use constructor injection instead of @Autowired:

    
    private KafkaTemplate<String,EventDto> kafkaTemplate;
    
    public EventProducer(KafkaTemplate<String,EventDto> kafkaTemplate, something) {
        this.kafkaTemplate = kafkaTemplate;
    }
    
    public EventDto sendEvent(Event event) {
           EventDto eventToSend = this.dtoMapper.mapToDto(event, SomeEvent.class);
           this.kafkaTemplate.send("topic",eventToSend);
           return eventToSend;
    }
    

    This way you can inject a mock in your tests:

    @Test
    void testSendEvent() {
          //omitted lines regarding second assert that works
          KafkaTemplate<<String,EventDto>> templateMock = mock(KafkaTemplate.class);
          EventProducer producer = new EventProducer(templateMock, something);
          EventDto dto = producer.sendEvent(Event.newBuilder().build());
          assertThat(dto).isNotNull();
          //there is a second assert here that passes, nothing to do with kafka
    }
    

    If you can't change the class' constructor, you can provide a mock using @MockBean:

    @MockBean
    KafkaTemplate<String,EventDto> kafkaTemplate;
    
    @Test
    void testSendEvent() {
          //omitted lines regarding second assert that works
          
          EventProducer producer = new EventProducer(something);
          EventDto dto = producer.sendEvent(Event.newBuilder().build());
          assertThat(dto).isNotNull();
          //there is a second assert here that passes, nothing to do with kafka
    }
    

    But there's something odd with this design - does the EventProducer class have @Autowired and constructor arguments? Autowiring only works on beans, and usually either the class has a default constructor and @Autowired dependencies, or injects everything through the constructor.

    If those options I present do not work for you, please add more details on the class' constructor and overall design.