Answer a question

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;
   }
}

Answers

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.

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐