I am trying to mock a method (getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())) to return qcchecks which I have defined in my test case but I get an error.
Any suggestions on where I am doing it wrong please
org.mockito.exceptions.base.MockitoException: Unable to create mock instance of type 'QualityControlChecksDataProvider'
QualityControlChecksDataProvider
class QualityControlChecksDataProvider #Inject constructor(
private val offlineDataStorage: OfflineDataStorage,
private val app: App
) {
private val mapOfQCChecksForMilestone = LinkedHashMap<String, ArrayList<QualityControlCheck>?>()
fun getQualityControlCheckDataForUplift(qualityControlMilestone: QualityControlMilestone, uplift: Float):
ArrayList<QualityControlCheck>? {
val qcChecksForUplift: ArrayList<QualityControlCheck>? = ArrayList()
val qcChecksForMilestone = mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
qcChecksForMilestone?.forEach {
if (it.uplift == uplift) qcChecksForUplift?.add(it)
}
return qcChecksForUplift
}
}
In my unit test this is how I am trying to mock
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlCheckDataProvider = spy(QualityControlChecksDataProvider::class.java)
every(qualityControlCheckDataProvider.getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())).thenReturn(qualityControlChecks)
}
I don't think a spy is what you want here I think you want a mock. Give this a try
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlDataProviderMock = mock<QualityControlChecksDataProvider> {
on { getQualityControlCheckDataForUplift(any(), any()) } doReturn qualityControlChecks
}
}
Related
I am unit testing the following class
class LoadTrendingSearchUseCaseImp #Inject constructor(
private val searchCriteriaProvider: SearchCriteriaProvider,
private val coroutineDispatcherProvider: CoroutineDispatcherProvider
) : LoadTrendingSearchUseCase {
override suspend fun execute(): List<String> {
return withContext(coroutineDispatcherProvider.io()) {
searchCriteriaProvider.provideTrendingSearch().trendingSearches
}
}
}
interface SearchCriteriaProvider {
suspend fun provideTrendingSearch(): CatalogSearchPage
}
class SearchCriteriaProviderImp() : SearchCritieraProvider {
override suspend fun provideTrendingSearch(): CatalogSearchPage {
return withContext(coroutineDispatcherProvider.io()) {
/* long running task */
}
}
}
interface CoroutineDispatcherProvider {
fun io(): CoroutineDispatcher = Dispatchers.IO
fun default(): CoroutineDispatcher = Dispatchers.Default
fun main(): CoroutineDispatcher = Dispatchers.Main
fun immediate(): CoroutineDispatcher = Dispatchers.Main.immediate
fun unconfined(): CoroutineDispatcher = Dispatchers.Unconfined
}
class CoroutineDispatcherProviderImp #Inject constructor() : CoroutineDispatcherProvider
This is my actual test:
class LoadTrendingSearchUseCaseImpTest {
private val searchCriteriaProvider: SearchCriteriaProvider = mock()
private val coroutineDispatcherProvider = CoroutineDispatcherProviderImp()
private lateinit var loadTrendingSearchUseCaseImp: LoadTrendingSearchUseCaseImp
#Before
fun setUp() {
loadTrendingSearchUseCaseImp = LoadTrendingSearchUseCaseImp(
searchCriteriaProvider,
coroutineDispatcherProvider
)
}
#Test
fun `should provide trending searches`() {
runBlockingTest {
// Arrange
// EXCEPTION HERE whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches).thenReturn(
emptyList()
)
// Act
val actualResult = loadTrendingSearchUseCaseImp.execute()
// Assert
assertThat(actualResult).isEmpty()
}
}
}
The actual error message:
java.lang.NullPointerException
.product_search.usecase.imp.LoadTrendingSearchUseCaseImpTest$should provide trending searches$1.invokeSuspend(LoadTrendingSearchUseCaseImpTest.kt:30)
You tried to chain invocations when stubbing a method.
whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches)
.thenReturn(emptyList())
During stubbing, the actual methods are being called.
searchCriteriaProvider.provideTrendingSearch() returns null, as this call is not stubbed yet
subsequent call null.trendingSearches results in NPE
You need to stub each call in the chain
whenever(searchCriteriaProvider.provideTrendingSearch())
.thenReturn(catalogSearchPage)
whenever(catalogSearchPage.trendingSearches)
.thenReturn(emptyList())
Obviously, this assumes that
catalogSearchPage is also a mock
trendingSearches is a property
Alternatively, you can construct a POJO for catalogSearchPage, and return it in the first stubbing.
I have a code where I am applying caching to get an object.
service:
#Service
class UserServiceImpl(
private val userRepository: UserRepository
) : UserService {
override fun create(userEntity: UserEntity): UserEntity = userRepository.save(userEntity)
.also { log.info("saved user {}", it) }
#Cacheable("users", key = "#id")
override fun get(id: Long): UserEntity = userRepository.findById(id)
.orElseThrow { EntityNotFoundException("User not found by id $id") }
.also { log.info("from db: received user {}", it) }
companion object {
private val log = KotlinLogging.logger { }
}
}
repository:
#Repository
interface UserRepository : JpaRepository<UserEntity, Long> {
}
I have verified with a simple controller that the caching works well, but I cannot verify this with tests. Test fails with an error: Verification failed: call 1 of 1: UserRepository(#1).findById(eq(1))). 3 matching calls found, but needs at least 1 and at most 1 calls
class UserServiceImplTest {
private val userRepository = mockkClass(UserRepository::class)
private val userService: UserService = UserServiceImpl(userRepository)
#Test
fun `get should use caching`() {
// given
val user = UserEntity(1, "Anna", "anna#gmail.com")
every { userRepository.save(user)} returns user
every { userRepository.findById(user.id!!) } returns Optional.of(user)
// when
userService.get(user.id!!)
userService.get(user.id!!)
userService.get(user.id!!)
// then
verify(exactly = 1) { userRepository.findById(user.id!!) }
}
}
Perhaps I need to somehow enable caching for tests too. Or my test is written incorrectly (which is most likely). How can I write a test to check that the caching is working?
#Cacheable will generate a wrapper method which does the caching. This wrapper will exist on the proxy generated by Spring, so it will not come into play when you create the UserServiceImpl yourself. If you want to test it, you need to let Spring context manage the classes, including the mock.
For instance,
#SpringBootTest
class UserServiceImplTest {
#MockBean
lateinit var userRepository: UserRepository
#Autowired
lateinit var userService: UserService
#Test
fun `get should use caching`() {
// given
val user = UserEntity(1, "Anna", "anna#gmail.com")
every { userRepository.save(user)} returns user
every { userRepository.findById(user.id!!) } returns Optional.of(user)
// when
userService.get(user.id!!)
userService.get(user.id!!)
userService.get(user.id!!)
// then
verify(exactly = 1) { userRepository.findById(user.id!!) }
}
}
using mockk 1.9.3
having a function to be verified
class EventLogger private constructor()
fun logUserEvent(eventName: String?, eventParamMap: MutableMap<String, String>?) {
......
internaLogEventImpl(eventName, eventParamMap)
}
internal fun internaLogEventImpl(eventName: String?, customParams: MutableMap<String, String>?) {
......
}
companion object {
#Volatile
private var sEventLoggerSingleton: EventLogger? = null
#JvmStatic
val instance: EventLogger
get() {
if (sEventLoggerSingleton == null) {
sEventLoggerSingleton = EventLogger()
}
return sEventLoggerSingleton!!
}
}
got compiler error at every {eventLogger.internaLogEventImpl(any(), mapSlot)}
Type mismatch.
Required: MutableMap<String, String>?
Found: CapturingSlot<MutableMap<String, String>>
when trying this below :
class TestK {
lateinit var eventLogger: EventLogger
lateinit var application: Application
val mapSlot = slot<MutableMap<String, String>>()
#Before
fun setUp() {
application = ApplicationProvider.getApplicationContext<Application>()
eventLogger = mockk.spyk(EventLogger.instance)
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", eventLogger)
}
#After
fun cleanUp() {
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", null)
}
#Test
fun logNotificationStatusChange_with_enabled_WhenCalled_ShouldLog() {
val testMap = hashMapOf("action" to "open")
every {eventLogger.internaLogEventImpl(any(), mapSlot)} answers {
println(mapSlot.captured)
assert(mapSlot.captured["action"] == "open")
}
eventLogger.logUserEvent("test_event", testMap)
}
}
You need to use capture (see Mockk's Capturing section).
So for your case capture(mapSlot) should work.
eventLogger.internaLogEventImpl(any(), capture(mapSlot))
Before trying to mock on complex code, It would be better to learn on easier example.
Here is a working example to mock a private call on an object with mockk.
object MyLogger {
fun logUserEvent(event: String?, map: MutableMap<String, String>?) {
// turns the event string into an uppercase string.
internaLogEventImpl(event?.toUpperCase(), map)
}
private fun internaLogEventImpl(event: String?, map: MutableMap<String, String>?): Unit =
throw Exception("real implementation")
}
How to test and mock the internal function so we don't throw the exception.
#Test
fun `test logger internal`() {
val expectedMap = mutableMapOf("a" to "b")
val expectedEvent = "EVENT"
val mock = spyk(MyLogger, recordPrivateCalls = true)
justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
// or justRun { mock["internaLogEventImpl"](any<String>(), any<MutableMap<String, String>>()) }
mock.logUserEvent("event", expectedMap)
verify { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
}
Here logUserEvent calls the real implementation and internaLogEventImpl calls the mock implementation.
if justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) } is not called (or wrong because the argument doesn't match), the real implementation will be call. Here it will throw Exception("real implementation").
Please try and modify values to check different behaviors.
I have the following classes
interface CarsApi {
suspend fun fetchCar() : Car
}
class FetchCarUseCase(private val carsApi: CarsApi) {
suspend fun execute: Car = withContext(dispatcherProvider.io()) {
carsApi.fetchCar()
}
}
class ViewModel(private val fetchCarUseCase: FetchCarUseCase) {
private var car: Car
suspend fun retrieveCar() {
car = fetchCarUseCase.execute()
}
}
I want to write an ermetic test for the viewModel and the useCase:
#Test
fun testCarFetching() = runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).thenReturn(aCar)
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on viewModel.car*/
}
But the viewModel.car always seems to be null. Inside the test body mockApi.fetchCar() does retrieve the provided value, but inside the FetchCarUseCase it does not. Also if I remove the suspend keyword from the interface, the mocking seems to be working fine.
At the moment, due to some other conditions I cannot use Mockk library, so I'm stuck with Mockito.
Am I missing something?
The used dependencies:
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.28.2'
testImplementation('com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0') {
exclude module: 'mockito-core'
}
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2
In case anyone else has to deal with this problem, here is the infrastructure I have build.
First, in all the classes that launch threads inject through the constructor or property a kotlinx.coroutines.DispatcherProvider. In my case it was just the useCase, but the viewModel might require it, as well.
class FetchCarUseCase(private val dispatcher: CoroutineDispatcher,
private val carsApi: CarsApi) {
suspend fun execute: Car = withContext(dispatcher) {
carsApi.fetchCar()
}
}
In the unit tests project, add a helper rule-class, in order to extract some functionality:
#ExperimentalCoroutinesApi
class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
val testDispatcherProvider = object : DispatcherProvider {
override fun default(): CoroutineDispatcher = testDispatcher
override fun io(): CoroutineDispatcher = testDispatcher
override fun main(): CoroutineDispatcher = testDispatcher
override fun unconfined(): CoroutineDispatcher = testDispatcher
}
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
And finally the unit test looks like this:
#ExperimentalCoroutinesApi
#RunWith(MockitoJUnitRunner::class)
class ViewModelTest {
#get:Rule
var coroutinesTestRule = CoroutineTestRule()
#Test
fun testCarFetching() = coroutinesTestRule.testDispatcher.runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).thenReturn(aCar)
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on viewModel.car*/
}
#Test
fun testCarFetchingError() = coroutinesTestRule.testDispatcher.runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).then {
throw Exception()
}
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on erros*/
}
}
This way all the code in the unit tests runs on the same thread and in the same context.
Is it possible to pass a specific value into an extension function with receiver, in a unit test?
I'm trying to test the folowing subscribe method:
...
private lateinit var subscription: SubscriptionReceiveChannel<UiStateModel>
suspend fun subscribe(model: MainViewModel) {
subscription = model.connect()
subscription.consumeEach { value -> loadView(value) /** or loadView(it) */ }
}
private fun loadView(uiState: UiStateModel) {
when(uiState) {
is Loading -> view.isLoading()
is Error -> view.isError(uiState.exception.localizedMessage)
is Success -> when {
uiState.result != null -> view.isSuccess(uiState.result)
else -> view.isEmpty()
}
}
}
I want to be able to apply a specific value to the consumeEach function, but how can this be done?
Here's my unit test:
...
private val view = mock<MainView>()
private val model = mock<MainViewModel>()
private val subscription = mock<SubscriptionReceiveChannel<UiStateModel>>()
private val presenter = MainPresenter(view)
#Test
fun `When uistate is loading, view should show loading message`() = runBlocking {
// Given
val state = UiStateModel.Loading()
whenever(model.connect()).thenReturn(subscription)
whenever(subscription.consumeEach { any() }).thenAnswer({ state })
// When
presenter.subscribe(model)
// Then
verify(model).connect()
verify(view).isLoading()
verify(view, never()).isSuccess(anyString())
verify(view, never()).isEmpty()
verify(view, never()).isError(anyString())
}
...