OK, I need advice. I have a service I’m trying to...
# getting-started
l
OK, I need advice. I have a service I’m trying to replace with a Fake for testing. It’s roughly like this:
Copy code
interface 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):
Copy code
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?
d
If you want to replace a bean in tests there's https://github.com/Ninja-Squad/springmockk (or Mockito equivalent, depending on which mocking framework you use).
l
I was trying to avoid using a mocking framework. We already have mockk() in use in various and sundry places, but I try to avoid mocking frameworks where I can.
And even with that, I don’t know how I’d do the “extra property” bit.
r
looks like you're using the extra property to capture the values that are passed to the
doStuff
function. With MockK you can achieve the same by using the Capturing functionality.
l
Correct. I only need to do very basic validation in this case.
With 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?
k
Why
fun I(): I = object : I {
instead of just
class FakeC : I {
?
Alternatively, move your
val received
into a static scope, like the file scope or
companion object
🤔 1
👎 1
r
but if you really don't want to use a mocking framework, you could make an explicit test implementation of the
I
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)
l
Either would work; I’m just used to using anon classes for testing purposes. A concrete class wouldn’t resolve the type mismatch issue, would it?
c
You can do this, you just have to declare the list outside of the object:
Copy code
fun 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"
l
@CLOVIS That won’t work since I am not using the I instance in the test directly. It’s an injected dependency of the service being tested.
r
With 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, here
k
A concrete class wouldn’t resolve the type mismatch issue, would it?
What type mismatch? I'm not sure what you refer to.
l
@Kristian Rekstad Then I’d do something like
TestConfiguration.received.has(...)
in my test to verify it was called? That feels… weird. 🙂
👍 1
c
Alternatively, move your
val received
into a static scope, like the file scope or
companion object
This will share your instances between multiple tests executions, I don't recommend it.
It’s an injected dependency of the service being tested.
This is a question for your service locator framework, then, not a Kotlin one.
l
Copy code
interface 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.
r
in your test class you can inject it as a
Fake
instead
k
Whether
Fake
is a concrete class or anon doesn’t make a difference, AFAIK.
Yes it does, if you want to add properties to it and access them later.
it’s known as an I there, not Fake, so I cannot use the
received
property.
Ask for a Fake, as Riccardo says, or use a cast:
I as Fake
🤔 1
l
Hm. Let me convert to a concrete class and try a cast. Will report back.
👍 1
Haha! That works!
The only slightly weird part is this:
val fakeSender = blindSender as TestConfiguration.FakeSender
(Since I’m using an inner class for the fake.) But… I’m OK with it.
c
You can import the nested class directly (ALT ENTER on FakeSender should have the option)
l
Sure, it’s more the oddity of TestConfiguration.something. Where the oddity is doesn’t really matter. 🙂 But this looks like the solution I was looking for. Thanks!
r
just to reiterate, in case you missed it: Spring caches application contexts across multiple tests (if they share the same context configuration), so make sure that you either: • don't reuse this test context across tests • or only use the
received
property in a single test • or reset the list after each test (e.g. in an
@AfterEach
function you call
received.clear()
😯 1
👍 1
c
Simplest version is to make a
FakeI
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.
Copy code
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.
c
Actually you don't need to have a
FakeI
interface 🤔
Copy code
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 }
}
d
Btw what is the nature of the pushback against mocking libraries? Because setting up your own mock/spy interfaces looks like you are creating your own mocking framework 🙂
c
The problem is bytecode manipulation. It makes logging/the debugger/etc unusable. Also, it's not KMP. I like my code to be code. Real code, that I can open in my IDE and read.
👍 2
l
1. Mocking libraries are a meta-DSL you have to learn that is basically undebuggable and frequently makes step-debugging mostly or totally useless. Interfaces should be all you need to write testable code, give or take niceties like anon classes or good casting, etc. If your code cannot be tested without a mocking framework, it’s a code smell. 2. Holy crap, you can put a TestConfiguration on an inner class? Does that override or add to a standalone TestConfiguration class??? 3. For now
interface I
,
class Fake: I
,
(service as Fake).received
does the job I need, so I went with that.
1
d
Re mocking framework: Yes, step debugging might be a little harder or almost impossible, depending on your knowledge of the code under debug 🙂 But other than that I've had no issues with mocking. Having to write interfaces for everything would be too much of a hassle for me. But of course, that's just my personal preference.
c
I also try to minimise my use of mocking. I generally prefer social tests with test doubles/mocks only when absolutely necessary with mocks being one of a number of tools that can be used.
My theory is that mocking frameworks, as good and clever as they are, made mocking "too easy" and that's partly why it's so abused in a number of cases.
l
Yeah, they’re a crutch that lets you test code that is sloppily written. I’d rather have the friction push me (and my team) to write less sloppy code in the first place. 🙂
d
lets you test code that is sloppily written
Are you talking here about an absence of interfaces or also anything else?
l
The most common issue I see is not using DI properly, or having interactions that are too complex for their own good. Anon classes (as long as the parent is open, not final) handle many of the no-interface cases effectively enough, though naturally not all. But given the choice between “add an interface to make testing easier” and “figure out the mocking meta-DSL”, I’ll almost always opt for the former if I can.
(Context: I’m still relatively new to Kotlin, but have 25 years experience in PHP, which aside from the lack of generics can be used in very similar ways to Kotlin. So there’s undoubtedly some edge cases and details that differ between the languages when it comes to testing that I haven’t fully run into yet.)
c
DI Frameworks such as Spring also make it really easy to abuse. Everything becomes a @Component and they're all passed to each others constructors and then because "you're supposed to mock dependencies" and "injected components are dependencies" you end up with mocks everywhere even for things that don't need mocking.
l
Indeed. Automagic can be useful in the right quantities, but too much just makes things worse. (In PHP land, that mainly means the Laravel framework.)
c
Yeah. Spring/Spring Boot is very similar in construction to Laravel or Symfony
l
To Symfony, yes. A lot of things look/feel very familiar. (Symfony took a lot of inspiration from Spring.) Laravel is quite different, though not in a good way. It’s more Rails-y.