I just noticed Kotest adds transitive dependency o...
# kotest
w
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
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
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
Sounds like a new project you should start 🙂
w
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
As to your question @wasyl you could exclude the dependency if you don't use that constant now extension.
👍 1
w
Thank, I’ll do that 🙂
s
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
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
I always try to point out when people "test the mock"
mock a method, invoke the method, method does what I want.
☝️ 1
c
you could just remove the constant now feature and tell people to inject a clock
s
I can't remove something that people might be using even if I wouldn't use it myself.
j
Maybe in 5.0 :P
w
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
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
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
which may work - extra repos or semver ?
w
I meant semver
s
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
are you going to seperate the property testing framework too?
s
Possibly. It's already a separate artifact
t
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
Copy code
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
If you have things like
Copy code
interface Foo { fun doThing() }
then using fake vs mock will have little practical difference. But consider
Copy code
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
Copy code
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
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
+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
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
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
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
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
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
Need to read up on this, any suggestions?
c
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
I complete agree with that. IO / logic - split as much as possible
👍 1
t
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
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
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
for cases like this the avoid-mocks rule is great as a help to not build a too complicated structure.