2

Here is my class under test:

KafkaProcessorApplication

@EnableBinding(Processor.class)
@EnableConfigurationProperties(KafkaProperties.class)
public class KafkaProcessorApplication {

    @Autowired
    private Processor processor;

    @Autowired
    private KafkaProperties kafkaProperties;

    private KafkaTemplate<String, String> kafkaTemplate;

    @Autowired
    KafkaProcessorApplication(SenderConfig senderConfig) {
        this.kafkaTemplate = senderConfig.kafkaTemplate();
    }

Here, SenderConfig is a just a simple config class with the method kafkaTemplate() creating a new instance of KafkaTemplate.

SenderConfig

@Configuration
public class SenderConfig {
@Autowired
KafkaProperties kafkaProperties;

public ProducerFactory<String, String> producerFactory() {
    return new DefaultKafkaProducerFactory<>(new HashMap());
}

public KafkaTemplate<String, String> kafkaTemplate() {
    return new KafkaTemplate<>(ProducerFactory()));
}

}

Here is the test class:

KafkaTestClass

@SpringBootTest
@ActiveProfiles("test")
@ContextConfiguration(classes = {SenderConfig.class, KafkaProcessorApplication.class})
@TestPropertySource(locations = "classpath:test-resources.properties")
@RunWith(SpringRunner.class)
public class KafkaProcessorApplicationTest {

    @Autowired
    private Processor processor;
    @Mock
    private SenderConfig senderConfig;

    @Mock
    private KafkaProperties kafkaProperties = new KafkaProperties();
    @Mock private KafkaTemplate mockKafka;

    @Autowired
    @InjectMocks
    private KafkaProcessorApplication app;

    @Before
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);
        doReturn("ServerConfig").when(kafkaProperties).getServersConfig();
        when(senderConfig.kafkaTemplate()).thenReturn(kafkaTemplate);
    }

I want to mock kafkaTemplate. But, its instantiation is in constructor which is being executed even before the @Before is executed, where the logic of mocking the method is written.

Just curious why is the constructor being executed first, and also, how can I mock the method if this is the case? What could be the approaches of mocking the kafkaTemplate, without using Powermock and without modifying the class under test as I can not change it?

KumarAnkit
  • 713
  • 1
  • 9
  • 26

1 Answers1

2

When you use @SpringBootTest, the Spring dependency tree is resolved before the @Before method has a chance to execute. This includes constructing the KafkaProcessorApplication bean and its dependencies. This is why the constructor runs before @Before.

What you want is Spring's @MockBean to create and inject a mock bean in the application context.

This question has a great write-up of how you can use this: Difference between @Mock, @MockBean and Mockito.mock()

update

Now I see. The problem is that the KafkaProcessorApplication accesses the mock in its constructor before you can configure it.

This can be solved with a separate test Spring configuration that will return a configured SenderConfig mock bean as described here: Testing spring bean with post construct

bernie
  • 9,820
  • 5
  • 62
  • 92
  • Ok, I have used many options to mock the bahaviour of the method being called, but it always remains null, or in the case of using MockBean, creates new instance, So, how do I define its behaviour? – KumarAnkit Jan 11 '19 at 07:18
  • Which bean do you want to mock exactly? It's a bit hard to tell with your current test code. That mock must have @MockBean applied. The example in the first answer to the question I linked to seems pretty good. – bernie Jan 11 '19 at 07:49
  • I want to mock KafkaTemplate which is being created in the constructor call. I want a clear way to do it, without changing the source code – KumarAnkit Jan 11 '19 at 07:52
  • Are you sure? It looks like SenderConfig is the **bean** you want to mock. I don't see any KafkaTemplate **bean**. – bernie Jan 11 '19 at 08:09
  • I can mock SenderConfig already, it is just that, it is instantiating the KafkaTemplate before it can mock the method call on this mocked senderConfig. – KumarAnkit Jan 11 '19 at 08:38
  • Thanks, it worked after I updated it to use the configuration class, it will load the beans and define their behavior before beans can be created. – KumarAnkit Jan 11 '19 at 10:10
  • @KumarAnkit No problem. Please remember to accept my answer! I see you don't usually accept answers to your questions. See here for more info https://stackoverflow.com/help/someone-answers and here https://stackoverflow.com/help/accepted-answer – bernie Jan 11 '19 at 10:36