Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Helpers to create mockks via factory functions #919

Open
codymikol opened this issue Sep 8, 2022 · 0 comments
Open

RFC: Helpers to create mockks via factory functions #919

codymikol opened this issue Sep 8, 2022 · 0 comments

Comments

@codymikol
Copy link

Background / Proposal

Recently, I've started using a new pattern to create my mockks, it looks something like this ( I'll explain new syntax at the end )

  fun makeSubject(
    fooService_getFoo: Stub<String> = returns("foo"),
    barService_getBar: Stub<String> = returns("bar"),
    barService_getPet: Stub<String> = returns("dog"),
  ) = FooBarService(
    
    fooService = mockk {
      every { getFoo() } calls fooService_getFoo
    },

    barService = mockk {
      every { getBar() } calls barService_getBar
      every { getPet() } calls barService_getPet
    },

  )

What this allows me to do when testing classes that use dependency injection is to create a set of "happy" default behaviors for all of the mockks injected into my class under test.

IE, I can create my class under test like this.

val fooBarService = makeSubject()
val result = fooBarService.getFooBar()
assertEquals("foo bar dog", result)

If I want to swap out functionality, I can do so like this

val fooBarService = makeSubject(fooService_getFoo = returns("cool"))
val result = fooBarService.getFooBar()
assertEquals("cool bar dog", result)

I find that this makes it very easy to test permutations of service failures / alternate responses in a way that is immutable with no chance of leaking into other test cases, which I see happen fairly often in tests that utilize beforeEachTest type lifecycle hooks /
mutation of mockks to achieve the same result.

New Syntax

Currently, I am using the following code for everything above, naming is totally up for discussion

calls

infix fun <T> MockKStubScope<T, T>.calls(stub: Stub<T>) = stub(this)

this is honestly the meat of the whole thing, it allows you to easily decouple the every {} from the answer as a function argument.
Most importantly, it doesn't obfuscate anything inside of MockKStubScope

IE, without the syntax sugar

    fooService_getFoo: Stub<String> = { returns("foo") },
    fooService_getFoo: Stub<String> = { answers { throw Exception("KaBoom!") } },

Stub

typealias Stub<T> = MockKStubScope<T, T>.() -> Unit

This is just a type alias that prevents you from needing to write MockKStubScope<T, T>.() -> Unit over and over again.

returns

fun <T> returns(value: T): Stub<T> = { returns(value) }

syntactic sugar so you don't need to = { returns(value) }

answers

fun <T> answers(answerScope: MockKAnswerScope<T, T>.(Call) -> T): Stub<T> = { answers(answerScope) }

syntactic sugar so you don't need to = { answers { firstArg() } }

Plugin

I'm also working on a plugin for my IDE that will find all functions used by dependencies for a given class and generate the boilerplate you see above.

IE, if those three stubs were used inside of fooBarService, my plugin would allow you to generate

  fun makeSubject(
    fooService_getFoo: Stub<String> = TODO(),
    barService_getBar: Stub<String> = TODO(),
    barService_getPet: Stub<String> = TODO(),
  ) = FooBarService(
    
    fooService = mockk {
      every { getFoo() } calls fooService_getFoo
    },

    barService = mockk {
      every { getBar() } calls barService_getBar
      every { getPet() } calls barService_getPet
    },

  )

This removes a lot of ceremony for testing classes that leverage DI

Question

Does this seem like something that would be beneficial to exist within the mockk ecosystem? I'd be happy to do or help with the implementation if this seems useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant