Kyle
Kyle

Reputation: 1524

How to use mockk with a generic constructor param?

So I have a class

class Test<T : SomeListener> @Inject constructor(
     private val dependency1: Dependency1,
     private val listener: T
)

I'm trying to write a unit test for it using mockk and running into an error when trying to mock and initialize it with the generic type.

class TestTest {
     @MockK
     lateinit var dependency1: Dependency1
     @MockK
     lateinit var listener: ListenerImpl

     @InjectMockKs(overrideValues = true)
     lateinit var testObject: Test<ListenerImpl>
}

I keep getting an error "io.mockk.MockKException: No matching constructors found: ... listener : T =

What is the right way to get it to mock the constructor correctly with this generic parameter value?

Upvotes: 2

Views: 2213

Answers (3)

G. Lasne
G. Lasne

Reputation: 79

I uses something like below in a SpringBoot context, injecting the parameters should work:

@ExtendWith(MockKExtension::class)
class SomeTestTest(
    @MockK private val mockDependency1: Dependancy1,
    @MockK private val mockListener: ListenerImpl
) {
    @InjectMockKs
    lateinit var testObject: SomeTest<Listener>

    @Test
    fun test() {
        every { mockDependency1.test() } returns "test"
        every { mockListener.listen(any()) } returns Unit
        testObject.test()
        verify { mockListener.listen("test") }
    }
}

@Service
class SomeTest<Listener>
    (
    private val dependancy1: Dependancy1,
    private val listener: ListenerImpl
)
{
        fun test(): String {
        val s = dependancy1.test()
        listener.listen(s)
        return s
    }
}

interface Listener {
   fun listen(s: String): Unit
}

@Service
class Dependancy1 {
   fun test() = "test"
}

@Component
class ListenerImpl: Listener {
   override fun listen(s: String): Unit {
      println(s)
   }
}

Upvotes: 0

ela16
ela16

Reputation: 857

You can do this by instantiating the service/class that you wish to test upon setup and pass the mocks as paramters. This is an alternative approach to using annotations like @MockK and @InjectMocks. Here is how you would do it:

import io.mockk.mockk
import io.quarkus.test.junit.QuarkusTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeEach


@QuarkusTest
class TestTest {
    // Instantiate the dependency mocks to be able to pass them as parameters
    private val dependency1 = mockk<Dependency1>(relaxed = true)

    private val listener = mockk<ListenerImpl>(relaxed = true)

    @BeforeEach
    fun setup() {
       // Instantiate the class under test, and pass the mocks from above
       testObject = Test<ListenerImpl>(depencency1, listener)
    }

    // Write your tests here
    @Test
    fun firstMethodTest() {
        // Setup 
        // ...
 
        // When you want to call the testObject, simply do
        testObject.firstMethod()
    }

}

Note: the (relaxed = true) parameter is optional, it simply provides a default value for any method that you haven't provided a mock return value for.

Upvotes: 0

Kyle
Kyle

Reputation: 1524

Unfortunately I wasn't able to figure out a way to purely do it with annotations. What I ended up doing was:

class TestTest {
     @MockK
     lateinit var dependency1: Dependency1
     @MockK
     lateinit var listener: ListenerImpl

   
     lateinit var testObject: Test<SomeListener>

     @BeforeEach
     fun setUp() {
         testObject = Test(
              dependency1,
              listener
          )
     }
}

This worked and initialized the testObject correctly.

Upvotes: 0

Related Questions