Larry Garfield
07/19/2024, 2:27 PMinterface I {
fun doStuff(arg: String)
}
class C : I {
override fun doStuff(arg: String) { ... }
}
For testing (in Spring), I’m trying to do this (which is a pattern I’m used to from PHP):
class TestConfiguration {
@Bean
@Primary
fun I(): I = object : I {
val received = mutableListOf<String>()
override fun doStuff(arg: String) {
received.add(arg)
}
}
}
The idea being that I can then, in a test, grab that service and check to see what it got called with. I’ve done this a fair bit in PHP, which allows for “squishy” types. In Kotlin, of course, I cannot access the property because it’s not defined in the interface, so the compiler cannot guarantee it’s going to be there. (Which is both correct and untrue, in this case.) How should I go about modifying this approach to be more Kotlin-esque?David Kubecka
07/19/2024, 2:39 PMLarry Garfield
07/19/2024, 2:45 PMLarry Garfield
07/19/2024, 2:45 PMRiccardo Lippolis
07/19/2024, 2:52 PMdoStuff
function. With MockK you can achieve the same by using the Capturing functionality.Larry Garfield
07/19/2024, 2:53 PMLarry Garfield
07/19/2024, 2:58 PMKristian Rekstad
07/19/2024, 2:58 PMfun I(): I = object : I {
instead of just class FakeC : I {
?Kristian Rekstad
07/19/2024, 2:59 PMval received
into a static scope, like the file scope or companion object
Riccardo Lippolis
07/19/2024, 2:59 PMI
interface, e.g. class CapturingI : I { ... }
, and in your Spring test inject the instance of CapturingI
and read the received
property. But make sure that the test is properly cleaned up (Spring caches test contexts across multiple tests if possible, so the tests could pollute each other)Larry Garfield
07/19/2024, 2:59 PMCLOVIS
07/19/2024, 2:59 PMfun fakeI(): Pair<I, List<String>> {
val received = mutableListOf<String>()
val obj = object : I {
override fun doStuff(arg: String) {
received.add(arg)
}
}
return obj to received
}
// Usage
val (i, received) = fakeI()
i.doStuff("foo")
received shouldContain "foo"
Larry Garfield
07/19/2024, 3:00 PMRiccardo Lippolis
07/19/2024, 3:01 PMWith the mockk() capturing version, how would I setup the mock in a bean but verify the data in the test? It looks like it runs into the same type issue, doesn’t it?the mock is configured in the
TestConfiguration
, but the expectation on the mock (which passes the capturing parameter) is defined in the test itself, and can be read there. Not sure which type issue you would be referring to, hereKristian Rekstad
07/19/2024, 3:01 PMA concrete class wouldn’t resolve the type mismatch issue, would it?What type mismatch? I'm not sure what you refer to.
Larry Garfield
07/19/2024, 3:02 PMTestConfiguration.received.has(...)
in my test to verify it was called? That feels… weird. 🙂CLOVIS
07/19/2024, 3:02 PMAlternatively, move yourThis will share your instances between multiple tests executions, I don't recommend it.into a static scope, like the file scope orval received
companion object
CLOVIS
07/19/2024, 3:02 PMIt’s an injected dependency of the service being tested.This is a question for your service locator framework, then, not a Kotlin one.
Larry Garfield
07/19/2024, 3:04 PMinterface I
class Real : I
class Fake : I {
val received: MutableList
}
My object-under-test expects an I, so that’s what the Bean configuration is typed to return. In my test class, I also get the bean injected so that I can introspect it afterward… but it’s known as an I there, not Fake, so I cannot use the received
property. Whether Fake
is a concrete class or anon doesn’t make a difference, AFAIK.Riccardo Lippolis
07/19/2024, 3:06 PMFake
insteadKristian Rekstad
07/19/2024, 3:06 PMWhetherYes it does, if you want to add properties to it and access them later.is a concrete class or anon doesn’t make a difference, AFAIK.Fake
it’s known as an I there, not Fake, so I cannot use theAsk for a Fake, as Riccardo says, or use a cast:property.received
I as Fake
Larry Garfield
07/19/2024, 3:08 PMLarry Garfield
07/19/2024, 3:13 PMLarry Garfield
07/19/2024, 3:13 PMval fakeSender = blindSender as TestConfiguration.FakeSender
(Since I’m using an inner class for the fake.) But… I’m OK with it.CLOVIS
07/19/2024, 3:15 PMLarry Garfield
07/19/2024, 3:16 PMRiccardo Lippolis
07/19/2024, 3:17 PMreceived
property in a single test
• or reset the list after each test (e.g. in an @AfterEach
function you call received.clear()
Riccardo Lippolis
07/19/2024, 3:18 PMCharles Flynn
07/20/2024, 8:48 AMFakeI
interface.
Have your test primary bean return I
but an object that implements FakeI
(doesn't need to be a class but bear in mind the concerns about spring tests and caching etc).
Then just autowire the bean back as FakeI
.
Example of the test. I've not included the actual interfaces/spring app code - it's easy enough to infer.
interface FakeRepo : Repository {
fun getReceived(): String
}
@SpringBootTest
class ObjectExampleApplicationTests {
@TestConfiguration
class TestConfig {
@Bean
@Primary
fun fakeRepo(): Repository = object : FakeRepo {
var receivedStr = ""
override fun getReceived(): String {
return receivedStr
}
override fun save(data: String) {
receivedStr = data
}
}
}
@Autowired
lateinit var service: Service
@Autowired
lateinit var fakeRepo: FakeRepo
@Test
fun `check hello world is passed to the repo`() {
service.doStuff()
assertEquals("hello world", fakeRepo.getReceived())
}
}
I get an IntelliJ warning that it can't autowire fakeRepo
but it does work.CLOVIS
07/21/2024, 12:21 PMFakeI
interface 🤔
interface I {
fun foo(arg: String)
}
// Let's have a second example
interface I2 {
fun foo2(arg: String)
}
// 'spy' not 'fake' because <https://martinfowler.com/bliki/TestDouble.html>
// Only need one for all tests
interface Spy {
val received: List<String>
}
val fakeI = object : I, Spy {
override val received = ArrayList<String>()
override val foo(arg: String) { received += arg }
}
val fakeI2 = object : I2, Spy {
override val received = ArrayList<String>()
override val foo2(arg: String) { received += arg }
}
David Kubecka
07/22/2024, 8:07 AMCLOVIS
07/22/2024, 12:07 PMLarry Garfield
07/22/2024, 1:20 PMinterface I
, class Fake: I
, (service as Fake).received
does the job I need, so I went with that.David Kubecka
07/22/2024, 1:29 PMCharles Flynn
07/22/2024, 1:31 PMCharles Flynn
07/22/2024, 1:31 PMLarry Garfield
07/22/2024, 1:41 PMDavid Kubecka
07/22/2024, 1:44 PMlets you test code that is sloppily writtenAre you talking here about an absence of interfaces or also anything else?
Larry Garfield
07/22/2024, 1:47 PMLarry Garfield
07/22/2024, 1:48 PMCharles Flynn
07/22/2024, 1:59 PMLarry Garfield
07/22/2024, 2:00 PMCLOVIS
07/22/2024, 2:02 PMLarry Garfield
07/22/2024, 2:03 PM