How to mock a private function in android test with MockK? - kotlin

I can't seem to mock private functions in android tests. I'm also using the all-open plugin for pre-P testing. On non-android tests it runs with no problems. I figured it should work on android too, because it's marked on MockK-android. Is this not implemented or am I missing something obvious?
androidTestImplementation "io.mockk:mockk-android:1.8.7"
#OpenForTesting
class A {
fun publicFun() = privateFun()
private fun privateFun() {}
protected fun protectedFun() {}
}
#Test
fun privateFunctionMock() {
val spy = spyk<A>()
val mock = mockk<A>()
val a = A()
val functions = a::class.functions // size -> 6
val spyFunctions = spy::class.functions // size -> 5
val mockFunctions = mock::class.functions // size -> 5
every { spy["privateFun"]() } returns Unit
a.publicFun()
}
Fails with Exception, because the private function is missing.
io.mockk.MockKException: can't find function privateFun() for dynamic call

Subclassing is employed to create mocks and spies for pre-P android instrumented tests. That means basically private methods are skipped because it is not possible to inherit them. That way counters are not counting private methods.

InternalPlatformDsl.dynamicSet(autoBannerViewPagerMock, "mBannerList", list)
every { autoBannerViewPagerMock.invoke("loadCoverImage") withArguments listOf(any<Int>(), any<Int>(), any<ImageView>(), any<stMetaBanner>()) } returns Unit

Related

Is it possible to disable inlining of value classes in Kotlin?

Goal
I would like to globally disable inlining of #JvmInline value class classes via a compiler flag or something similar. I would want to do this when running unit tests but not in production.
Motivation
I would like to use mockk with value classes.
I want to write a unit test that looks like this:
#JvmInline
value class Example(private val inner: Int)
class ExampleProvider {
fun getExample(): Example = TODO()
}
#Test
fun testMethod() {
val mockExample = mockk<Example>()
val mockProvider = mockk<ExampleProvider> {
every { getExample() } returns mockExample
}
Assert.assertEquals(mockExample, mockProvider.getExample())
}
This code fails with the following exception:
no answer found for: Example(#4).unbox-impl()
I think that if I were able to disable class inlining that this would no longer be an issue.

How to create a TestContainers base test class in Kotlin with JUnit 5

I am trying to use Neo4j TestContainers with Kotlin, Spring Data Neo4j, Spring Boot and JUnit 5. I have a lot of tests that require to use the test container. Ideally, I would like to avoid copying the container definition and configuration in each test class.
Currently I have something like:
#Testcontainers
#DataNeo4jTest
#Import(Neo4jConfiguration::class, Neo4jTestConfiguration::class)
class ContainerTest(#Autowired private val repository: XYZRepository) {
companion object {
const val IMAGE_NAME = "neo4j"
const val TAG_NAME = "3.5.5"
#Container
#JvmStatic
val databaseServer: KtNeo4jContainer = KtNeo4jContainer("$IMAGE_NAME:$TAG_NAME")
.withoutAuthentication()
}
#TestConfiguration
internal class Config {
#Bean
fun configuration(): Configuration = Configuration.Builder()
.uri(databaseServer.getBoltUrl())
.build()
}
#Test
#DisplayName("Create xyz")
fun testCreateXYZ() {
// ...
}
}
class KtNeo4jContainer(val imageName: String) : Neo4jContainer<KtNeo4jContainer>(imageName)
How can I extract the databaseServer definition and the #TestConfiguration? I tried different ways of creating a base class and having the ContainerTest extend it, but it is not working. From what I understand, static attriubutes are not inherited in Kotlin.
Below my solution for sharing same container between tests.
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class IntegrationTest {
companion object {
#JvmStatic
private val mongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.waitingFor(HostPortWaitStrategy())
#BeforeAll
#JvmStatic
fun beforeAll() {
mongoDBContainer.start()
}
#JvmStatic
#DynamicPropertySource
fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.data.mongodb.host", mongoDBContainer::getHost)
registry.add("spring.data.mongodb.port", mongoDBContainer::getFirstMappedPort)
}
}
}
The key here is to not use #Container annotation as it will close just created container after your first test subclass executes all tests.
Method start() in beforeAll() initialize container only once (upon first subclass test execution), then does nothing while container is running.
By theory we shouldn't have to do this hack, based on:
https://www.testcontainers.org/test_framework_integration/junit_5/
...container that is static should not be closed until all of tests of all subclasses are finished, but it's not working that way and I don't know why. Would be nice to have some answer on that :).
I've had the same issue (making Spring Boot + Kotlin + Testcontainers work together) and after searching the web for (quite) a while I found this nice solution: https://github.com/larmic/testcontainers-junit5. You'll just have to adopt it to your database.
I faced very similar issue in Kotlin and spring boot 2.4.0.
The way you can reuse one testcontainer configuration can be achieved through initializers, e.g.:
https://dev.to/silaev/the-testcontainers-mongodb-module-and-spring-data-mongodb-in-action-53ng or https://nirajsonawane.github.io/2019/12/25/Testcontainers-With-Spring-Boot-For-Integration-Testing/ (java versions)
I wanted to use also new approach of having dynamicProperties and it worked out of a boxed in java. In Kotlin I made sth like this (I wasn't able to make #Testcontainer annotations working for some reason). It's not very elegant but pretty simple solution that worked for me:
MongoContainerConfig class:
import org.testcontainers.containers.MongoDBContainer
class MongoContainerConfig {
companion object {
#JvmStatic
val mongoDBContainer = MongoDBContainer("mongo:4.4.2")
}
init {
mongoDBContainer.start()
}
}
Test class:
#SpringBootTest(
classes = [MongoContainerConfig::class]
)
internal class SomeTest {
companion object {
#JvmStatic
#DynamicPropertySource
fun setProperties(registry: DynamicPropertyRegistry) {
registry.add("mongodb.uri") {
MongoContainerConfig.mongoDBContainer.replicaSetUrl
}
}
}
Disadvantage is this block with properties in every test class what suggests that maybe approach with initializers is desired here.

unit testing extension function and mocking the other methods of the class

I am writing an extension function adding some retry capabilities to AmazonKinesis.putRecords. In my extension method i do some logic and some calls to the original putRecords method:
fun AmazonKinesis.putRecordsWithRetry(records: List<PutRecordsRequestEntry>, streamName: String) {
//...
val putRecordResult = this.putRecords(PutRecordsRequest().withStreamName(streamName).withRecords(records))
//...
}
From a unit test point of view I am finding it hard to see how I should mock the call to this.putRecords
I am using com.nhaarman.mockitokotlin2.*
val successfulRequest = PutRecordsResultEntry().withErrorCode(null);
class KinesisExtensionTest : StringSpec({
val testRecords = ArrayList<PutRecordsRequestEntry>()
testRecords.add(PutRecordsRequestEntry().withPartitionKey("iAmABunny").withData(ByteBuffer.wrap("aaa".toByteArray()))
)
val kinesis = mock<AmazonKinesis>{
on { putRecordsWithRetry(testRecords, "/dev/null") }.thenCallRealMethod()
on { putRecords(any()) }.thenReturn(PutRecordsResult().withRecords(listOf(successfulRequest, successfulRequest)))
}
"can write a record" {
kinesis.putRecordsWithRetry(testRecords, "/dev/null")
verify(kinesis).putRecord(any())
}
})
The putRecordResult is always null
The extension function AmazonKinesis.putRecordsWithRetry will be compiled to a static function under the hood, and Mockito doesn't support static method mocking yet.
Therefore Mockito may not know the stubbing information at the verification step and thus the default null value is produced.

How to inject dependency using koin in top level function

I have top-level function like
fun sendNotification(context:Context, data:Data) {
...//a lot of code here
}
That function creates notifications, sometimes notification can contain image, so I have to download it. I`m using Glide which is wrapped over interface ImageManager, so I have to inject it. I use Koin for DI and the problem is that I cannot write
val imageManager: ImageManager by inject()
somewhere in my code, because there is no something that implements KoinComponent interface.
The most obvious solution is to pass already injected somewhere else imageManager as parameter of function but I dont want to do it, because in most cases I dont need imageManager: it depends on type of Data parameter.
Easiest way is to create KoinComponent object as wrapper and then to get variable from it:
val imageManager = object:KoinComponent {val im: ImageManager by inject()}.im
Btw its better to wrap it by some function, for example I use
inline fun <reified T> getKoinInstance(): T {
return object : KoinComponent {
val value: T by inject()
}.value
}
So if I need instance I just write
val imageManager:ImageManager = getKoinInstance()
or
val imageManager = getKoinInstance<ImageManager>()
I did it in this way
fun Route.general() {
val repo: OperationRepo by lazy { GlobalContext.get().koin.get() }
...
}

How to correct "verify should appear after all code under test has been exercised" when verify is last?

I get the error "verify should appear after all code under test has been exercised" with the following:
class CowTest extends MockFactory {
Cow.init(testCowProcesses)
#Test
def noProcessesTest: Unit = {
val cow: Cow = Cow(testCowProcesses)
cow.simulateOneDay(0 nanoseconds)
}
#Test
def processSimulationTest: Unit = {
val NUMBER_OF_TRIES: Int = 10
val cow: Cow = Cow(testCowProcesses)
for (ii <- 0 until NUMBER_OF_TRIES) {
cow.simulateOneDay(0 nanoseconds)
}
(cow.metabolicProcess.simulateOneDay _).verify(0 nanoseconds).repeated(NUMBER_OF_TRIES)
}
}
testCowProcesses is defined in another file, like this (abbreviated):
object CowTesters extends MockFactory {
val metProc = stub[MetabolicProcess]
(metProc.replicate _).when().returns(metProc)
val testCowProcesses = CowProcesses(metProc)
}
I don't quite understand the error message. If I comment out the verify line, the test runs. Alternatively, if I comment out the first test, the second test can run. There are no other tests in the test class. This seems to indicate that the stub objects cannot be reused, as they were in mockito (I'm adapting code from mockito).
Is the best solution to reinstantiate the mock objects, perhaps by converting CowTesters into a class?
Edit:
I confirmed the above suggestion works (not sure if it is the best), but in the mean time I did something a bit more convoluted to get me through compiles:
//TODO: once all tests are converted to ScalaMock,
//TODO: just make this a class with a companion object
trait CowTesters extends MockFactory {
val metProc = stub[MetabolicProcess]
(metProc.replicate _).when().returns(metProc)
val testCowProcesses = CowProcesses(metProc)
}
object CowTesters extends CowTesters {
def apply(): CowTesters = new CowTesters {}
}
From your code above, it seems you are either trying to use JUnit or TestNG. ScalaMock doesn't support either of those frameworks directly, which is why you are struggling with the verification of mocks.
You need to implement your tests using either ScalaTest, or Specs2. See http://scalamock.org/user-guide/integration/
The conversion from JUnit to ScalaTest should be pretty straightforward if you switch to e.g. a FunSuite: http://www.scalatest.org/user_guide/selecting_a_style