Hello guys, we have a little problem with mocking ...
# test
c
Hello guys, we have a little problem with mocking in out unit tests. It looks a bit like an error per design in kotlin. but actually, i cant believe this πŸ˜„ we tried mockk and mockito regarding this and both result in the same problem. imagine we have an instance of the following class:
Copy code
data class AppConfig(
    var smtp = SmtpConfig(
            enabled = false
            host = "test",
            port = 12345,
            useStartTLS = true,
            fromName = "test",
            from = "<mailto:test@test.de|test@test.de>"
            user = ""
            password = ""
    )
)

val classInstance = AppConfig()
what I want to do in one of our tests is to mock the value of smtp. What I did - here for example with mockito - was the following:
Copy code
val spy = Mockito.spy(classInstance)
Mockito.`when`(spy.smtp).thenReturn(SmtpConfig(true)). //true is the enabled flag in this case, so the rest should be null
What Mockito and Mockk are doing in this case is to mock the get method behind that property. This can be verified by starting the debugger right in the line after the Mockito.\`when\` expression. The field is exactly like initialized above and when I click the getter in the debugger I get the result from spy.smtp, as you can see in the screenshot. I can understand why this happens, but kotlin simply does not use the getters of those properties why the tests wont work. So long text, simple question: Any idea how to fix this? Everything I found was a github issue from mockk with exactly the same problem and that has been closed because to support this a major rebuild would be necessary.
j
can't you just create an
AppConfig
instead? e.g. if you have an
SmtpConfig
in a variable called
x
then you can just
AppConfig(x)
and πŸͺ„
also can I suggest that you might want to make
var smtp
->
val smtp
-- an app's config changing while it's running might lead you to some tricky to debug problems πŸ™‚
c
actually it is a val, that was just in the example here, the acutal config is also much larger πŸ˜„ we tried it with the constructor. the problem is that the actual config is filled by a config file. so we have something like
Copy code
data class AppConfig(
    var smtp = SmtpConfig(
            enabled = getValueFromConfigFile()
            host = getValueFromConfigFile(),
            port = getValueFromConfigFile(),
            useStartTLS = getValueFromConfigFile(),
            fromName = getValueFromConfigFile(),
            from = getValueFromConfigFile()
            user = getValueFromConfigFile()
            password = getValueFromConfigFile()
    )
)
and what we want is a simple AppConfig with useful default values and override some of them in test classes. we tried something with a constructor but this modifies the actual production code to much without a useful reason or it it crashes with the dependency injection
what we could do is to create a complete mock class of app config and override the return values in the specific test classes. that definitly would be possible. but that sounds so wrong for something that just works in other languages πŸ˜„
j
πŸ‘ I guess a lot here depends on the context. So for example in systems I build I'd generally have a simple way to create a complete test configuration, so if I want a specific change I can just use the copy function and override the value I want. What you're looking for sounds like perhaps you might be passing in the whole config to something that just needs a subset of the values though β€” perhaps you could consider just passing the confit that the target needs rather than the whole config / smtp config object? (But again, not sure of the exact context, so this might not be a good idea!)
Making production code more easily testable is a good reason to change it!
c
We already tried that, that didn't work in the context either 😞 Making production code more testable is definitly a good idea. We worked a lot on doing this. But to us this problem sounds like we have to change the business code to have a workaround for a problem in our tests which is something totally different than refactoring crap so it can be testable. At least in our eyes. Especially because what we want works in every other language we currently use or use before. I really think this is a kotlin specific issue because kotlin goes the shortcut with properties and uses the backing fields. Java always for example uses the getters which results in perfectly working mocks. I really guess we have to build something around this problem...but overall I find it really interesting. Kotlin uses in the core the backing fields and not the getters. So plenty of mocking frameworks should have this issue. I'm asking myself: Why is this issue not big enough that there is an improvement somewhere? Are we holding something wrong or why do only a frew people also have this problem? πŸ˜„
thanks for the answers though!
j
and what we want is a simple AppConfig with useful default values and override some of them in test classes.
if you don't want to put "useful default values" into the constructor directly, could you instead have a test config file that contains them, and then use
.copy(..)
to override them?
c
I thought about that myself, but probably not. the problem in this case is, that the config gets injected in plenty of classes pretty early during test initialization. and currently I don't see a way how to "re-inject" a copy of that config : /
j
I guess you're using dependency injection via a container, e.g. spring or something, rather than just using constructors πŸ™‚
c
yep, we are using guice
j
tbh I'm a bit confused about how you can replace
smtp
with a mock, but not with a real version you hydrate from configuration or just
new
up?
but I don't know guice at all, so 🀷
this kind of thing is the reason I prefer to avoid dependency injection frameworks (in favour of a simple manual wiring phase during app start-up) πŸ˜„
c
i probably could do that but then I have to change all the config properties from vals to vars, so I can re-assign them. And as you already said: That sounds wrong πŸ˜„
j
but how can you make it a mock without reassigning it?
c
I'm not doing a complete mock, I create a spy. so I can use the full mock functionality on a "real" object
j
I guess you're in an area of mocking frameworks that I don't normally use πŸ™‚ So I guess you're using the mocking framework to make that
val
effectively be a
var
, but just in the production code it's not? πŸ™‚
c
yes, correct. In the production code it is a val (configs shouldn't be re-assignable), but on a spy inside a test I just can pretend that a function returns whatever I want. So no change in business logic, but still everything testable. The only problem is the conflict with that is kotlin at the moment because kotlin directly accesses the fields and the mocking libs only the getters
j
is it possible for production code to reference the injected value straight after injection but before your test executes?
c
In mockito it is pretty great to see. If I mock the smtp field and tell via mockito to return a stub object, then I can see inside an assertion that the stub works. but inside the business code it doesnt work. the reason is that the business code is kotlin which accesses the field directly and the assertion is mockito built in java which uses the getter to access the stub. So in that case I have the exact same object (the internal object IDs proof that), with the exact same call (config.smtp) but with different results in production code and inside the assertion πŸ˜„
j
it sounds like the injection phase is happening before your test executes, but your test has configuration it needs to inject, right? so to compensate for the injection happening too early you're trying to overwrite your configuration during the test, after things have already been configured.
c
exactly πŸ˜„
j
If you could bring the injection phase forwards to after the test starts, the test could contribute its required configuration at the appropriate time so that an overwrite isn't necessary
that would also mitigate the risk that doing the configuration overwrite brings (e.g. possibility of values being dereferenced by the injectee before the test overwrites them?)
(not familiar with guice so I don't know about practicalities here πŸ˜„ )
c
yes, this would totally help, but to me it looks like this is not possible. Something to replace and object in the DI container with a new object probably would help, but I didn't figure out how to do that or even if it's possible with guice. maybe I put some effort in checking this. this sounds more managable than working around the kotlin field access gehavior πŸ˜„
j
another possibility could be to look at test scoping -- is this test perhaps something that could be tested at a different level. e.g. an end to end test that spins up a running copy of the whole app but talks to a fake smtp server (i.e. a process running locally or in the same jvm that really listens to a network port and talks enough SMTP for the purposes of testing). (possibly a lower level test might be possible too but I don't know what you're actually trying to test so I went for the test that I'd probably want at least one of in a service that talks SMTP!)
c
nope, not possible either. This just tests if a specific function would send a mail that we expect. pretty simple. The config just needs the smtp values to be filled so the production code doesn't break and the tests expects a correct sender adrdess and a correct sender name
j
so you mean the email content, or the fact that it's sent at all, or both?
for the email content, a unit test should be good -- if that can't be written without wiring in config then that feels like a problem that can be resolved by extracting a class that takes the config as parameters
("takes the config as parameters" meaning takes the sending address and name as parameters)
c
both. that in theory it sends with the sender name, sender address and body that we expect
367 Views