Search code examples
javaunit-testingmockitospring-cloud-dataflow

Why is the constructor method being called before setup


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?


Solution

  • 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