Home > database >  Kotlin, Spring book, Mockito, @InjectMocks, Using different mocks than the ones created
Kotlin, Spring book, Mockito, @InjectMocks, Using different mocks than the ones created

Time:12-09

I am trying to test a class like

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(classes = [TestConfig::class])
@ExtendWith(MockitoExtension::class)
class TotalCalculatorTests {

@Mock
lateinit var jdbcRepo: JDBCRepo

@Mock
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>

@InjectMocks
lateinit var calculator: TotalCalculator

@Test
fun totalOrder() {
    // val calculator = TotalCalculator(userCache,jdbcRepo)
    calculator.total(ItemA(),ItemB())
    verify(jdbcRepo).getTotal()
 }
}

I get an error Actually, there were zero interactions with this mock. but if I uncomment the // val calculator = TotalCalculator(userCache,jdbcRepo) it works. I assumed mockito would be using the mocks from the test class but that appears to not be true. How can I get the instance of JDBCRepo being used by the @InjectMocks?

CodePudding user response:

It looks like you would like to run the full-fledged spring boot test with all the beans but in the application context you would like to "mock" some real beans and provide your own (mock-y) implementation.

If so, the usage of @Mock is wrong here. @Mock has nothing to do with spring, its a purely mockito's thing. It indeed can create a mock for you but it won't "substitute" the real implemenation with this mock implementation in the spring boot's application context.

For that purpose use @MockBean annotation instead. This is something from the spring "universe" that indeed creates a mockito driven mock under the hood, but substituted the regular bean in the application context (or even just adds this mock implementation to the application context if the real bean doesn't even exist).

Another thing to consider is how do you get the TotalCalculator bean (although you don't directly ask this in the question).

The TotalCalculator by itself is probably a spring been that spring boot creates for you, so if you want to run a "full fledged" test you should take the instance of this bean from the application context, rather than creating an instance by yourself. Use annotation @Autowired for that purpose:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(classes = [TestConfig::class])
@ExtendWith(MockitoExtension::class) // you don't need this
class TotalCalculatorTests {

@MockBean
lateinit var jdbcRepo: JDBCRepo

@MockBean
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>

@Autowired // injected by the spring test infrastructure
lateinit var calculator: TotalCalculator

@Test
fun totalOrder() {
    // val calculator = TotalCalculator(userCache,jdbcRepo)
    calculator.total(ItemA(),ItemB())
    verify(jdbcRepo).getTotal()
 }
}

Now, all this is correct if your purpose to indeed run the full microservice of spring boot and make an "integration" testing

If alternatively you just want to check the code of your calculator, this might be an overkill, you can end up using mockito and plain old unit testing. In this case however you don't need to even start the spring (read create an application context) during the test, and of course there is no need to use @SpringBootTest / @MockBean/@Autowired.

So it pretty much depends on what kind of test is really required here

  • Related