https://kotlinlang.org logo
Title
w

wasyl

03/17/2021, 4:56 PM
I just noticed Kotest adds transitive dependency on Mockk library. Seems like the only place it’s used is https://github.com/kotest/kotest/blob/99138d177bd52feea19740abeec6c5c4a3ae11c1/kot[…]mMain/kotlin/io/kotest/extensions/time/ConstantNowExtensions.kt. Is it safe to exclude this dependency? I consider mocking static methods, and basically everything Mockk is needed for a very bad practice, and I’d like to avoid accidentally having its agent on the classpath
4
s

sam

03/17/2021, 5:05 PM
I agree entirely that mocking is a code smell
2
We're in the minority tho
We would need some other way of achieving that we do in that constant time extension - or moving it to a new module in 4.5
c

christophsturm

03/17/2021, 5:15 PM
i wish there was a simple mock library for kotlin that can only mock interfaces. mockk is also very slow, the first invocation takes about 2 seconds.
☝️ 1
s

sam

03/17/2021, 5:15 PM
Sounds like a new project you should start 🙂
w

wasyl

03/17/2021, 5:16 PM
Regular
mockito
can by default only mock interfaces and open classes, which is good enough
Personally I migrating away from any mocks in favor of fake implementations. The only thing I don’t yet know how to mock are classes of external dependencies which don’t provide any testing utilities and for some reason I’d still like to unit test. There’s not many of those cases though
s

sam

03/17/2021, 5:18 PM
As to your question @wasyl you could exclude the dependency if you don't use that constant now extension.
👍 1
w

wasyl

03/17/2021, 5:18 PM
Thank, I’ll do that 🙂
s

sam

03/17/2021, 5:18 PM
I also prefer fake impls, except when you have to interface with some old school java Interface with 200,000 methods
and it's a pain to create a dummy impl
I have a 2 year project at work I haven't written a single mock for and it's got ~30 modules to it
w

wasyl

03/17/2021, 5:21 PM
We’re gradually removing all mocks from the project, and we already found several tests that were testing impossible cases or flows that don’t make any sense with the even the simplest fake implementations. Big fan 😄
s

sam

03/17/2021, 5:22 PM
I always try to point out when people "test the mock"
mock a method, invoke the method, method does what I want.
☝️ 1
c

christophsturm

03/17/2021, 5:23 PM
you could just remove the constant now feature and tell people to inject a clock
s

sam

03/17/2021, 5:24 PM
I can't remove something that people might be using even if I wouldn't use it myself.
j

Javier

03/17/2021, 5:25 PM
Maybe in 5.0 :P
w

wasyl

03/17/2021, 5:25 PM
Yeah I’d suggest removing too but literally two messages up I complained about not following semver for a change in the library 😄
But you could maybe deprecate it straight away to let people know it won’t be supported in the next breaking version anymore (unless you want to maintain it in a separate module)
s

sam

03/17/2021, 5:27 PM
well the semver thing was a mistake rather than a conscious decision
Also I don't think semver works on really big projects, or you end up release kotest 120.43.352
But we have begun the process of moving extensions and things to external repos, so the main kotest repo will be for the framework + basic assertions only, and so things can live on their own release cycle
👌 1
w

wasyl

03/17/2021, 5:29 PM
I think it may work, it just slows down the evolution significantly and adds a lot of cruft and effort for maintainers, like is the case with Gradle. But yeah I understand the matcher thing was not intential, no worries 🙂
s

sam

03/17/2021, 5:29 PM
which may work - extra repos or semver ?
w

wasyl

03/17/2021, 5:32 PM
I meant semver
s

sam

03/17/2021, 5:35 PM
yeah it's a good idea but like I say you end up with millions of version bumps. I try to stick to major.minor.bugfix as best I can but sometimes really tiny new features go into bugfix and sometimes big bugfixes go into minor, and then I only bump major for an (excuse the pun) major change
By moving the extensions and some assertions out to their own lifecycle we can do faster kotest releases anyway
👍 1
c

christophsturm

03/17/2021, 6:00 PM
are you going to seperate the property testing framework too?
s

sam

03/17/2021, 6:00 PM
Possibly. It's already a separate artifact
t

thanksforallthefish

03/18/2021, 6:56 AM
mock a method, invoke the method, method does what I want
how is this different than a fake though? implement a fake, call the method, it does what you want. I (am starting to) understand the no-mock movement, but I don’t see fakes as a replacement. mocks are just so much more flexible and can keep production code much more lean.
I would like to try to reduce mocking, but am actually not sure how
class DoSomethingUseCase(val repo: Repository) {
  fun doSomething(entityId: Id) {
    val entity = repo.get(entityId)
    entity.doSomething()
  }
}
imagine this trivial example, the only way I see to practical drop the mock of
Repository
is to drop the unit test for
DoSomethingUseCase
and replace with an integration test
w

wasyl

03/18/2021, 7:00 AM
If you have things like
interface Foo { fun doThing() }
then using fake vs mock will have little practical difference. But consider
interface Store {
  fun save(value: String)
  fun read(): String
}
When mocking this class you’ll either have to reimplement it basically but using mocks (usually it’s just carefully mocking
read()
function to return whatever one expects to have been stored) or you’ll end up testing all possible combinations of store/save return values, some of which might not make sense
class FakeRepository : Repository {
 
  private val entities = mutableMapOf<Id, Entity>()
 
  fun store(id: Id, entity: Entity) {
    entities[id] = entity
  }
 
  override fun get(id: Id) = entities[id]
}
this is simple and guaranteed to work as expected, honestly I don’t see reason to use a mock even in such simple scenario
t

thanksforallthefish

03/18/2021, 7:06 AM
that particular set of cases I “solved” with save methods always returning what they save, so you never (obviously never and always are never absolute) need to do save-and-read on the consumer side. depending on what tools you use, in your case, I would do integration test honestly. I am a heavy spring user, while it takes a while to instantiate a context, then such context is cached for all tests, as long as tests don’t start to need special contexts. not ideal for tdd, but am also not practicing tdd :p
w

wasyl

03/18/2021, 7:09 AM
+1 for integration tests overall, they’re just not practical sometimes. My favorite tests in our mobile app are tests of the entire networking layer with mock web server, where we only use like 2 methods of public API and check for returned values given server responses, and those tests cover several classes. But this is very specific case where inputs are well defined and the API surface is limited. But yeah, recently we started pulling simple classes and stores into unit tests and unit is now 3-5 classes. Highly recommend as well
t

thanksforallthefish

03/18/2021, 7:10 AM
I mean, I can definitely give it a try, to fakes I mean, you and Sam are really not the only ones believing in no mocks, I am just overall skeptical. fakes are just another (now untested) implementation of some code, mock is about behaviors, but it is true mocks can lead to unreal scenarios
w

wasyl

03/18/2021, 7:14 AM
fakes are just another (now untested) implementation of some code
fair point, my take is that if the fake is big enough to need a test then it implements interface that’s too big anyway, and the simple implementations are easily verifiable. I did hear an idea of testing fakes using the same tests as real implementations, but haven’t really seen a real example and I’m not sure it’s possible
c

christophsturm

03/18/2021, 9:07 AM
one good way to reduce mocking is to never do IO and logic in the same method. (functional core/imperative shell). so avoiding mocks leads to better structured code.
w

wasyl

03/18/2021, 9:10 AM
What do you mean IO? Specifically file access? Or to basically have functions with return values only return some value and only have logic in functions returning
Unit
?
c

christophsturm

03/18/2021, 9:12 AM
IO in that case is anything that has side effects. anything you may want to mock. all logic should be in code that works only with data structures
w

wasyl

03/18/2021, 9:15 AM
Need to read up on this, any suggestions?
c

christophsturm

03/18/2021, 9:15 AM
theres a screencast about it that i really like, the code is in ruby but the concept is explained great:https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell
👍 2
👍🏼 1
🙇 1
👍🏻 1
s

sam

03/18/2021, 9:38 AM
I complete agree with that. IO / logic - split as much as possible
👍 1
t

thanksforallthefish

03/18/2021, 12:53 PM
I’ll give it a look later, it seems to match my idea as well. though io is still part of your code and it has to work, so I guess for those needs you go into integration tests rather than unit. but will watch the screencast later
c

christophsturm

03/18/2021, 12:57 PM
just avoid conditionals in the methods that do IO. then they are pretty easy to test, and it probably makes sense to test them without mocking (with a real db, a real http request, etc)
d

dave08

03/18/2021, 1:00 PM
Well, it gets worse when you need a certain structure on the filesystem and have logic to work with results of lists of files, etc...
And then there's Android...
Sometimes things are simple enough to extract, in other times --- it gets VERY complicated...
c

christophsturm

03/18/2021, 1:31 PM
for cases like this the avoid-mocks rule is great as a help to not build a too complicated structure.