If my application has a bunch of dependencies whic...
# ktor
l
If my application has a bunch of dependencies which various routes, etc. need to use it, is there a way to wire those dependencies throughout the application so that they're accessible throughout? In Haskell, I might use some Reader monad transformer to apply all the layers from my environment. I know I can just use an
object
singleton, but I'd like to keep things more testable by providing concrete implementations of the environment/dependencies and pass to my route functions all the way from the
Application.module()
level. Hopefully that questions make sense. tl;dr looking for whether there's some ktor/kotllin-idiomatic way to enable routes throughout my application to access a particular instance of some dependency.
s
are you asking if you can do dependency injection in Kotlin?
l
Sorry, question may not have been very clear. Not whether it can be done, but what's a simple/idiomatic way to do it, perhaps specific to a ktor application, and without using something like koin
s
You don't need a framework to do DI, you just... do it. You write your service classes/functions/dependents so that they accept their dependencies either via their constructors, factory methods, and/or function signatures, and then you wire up real dependencies in your application code, organized however you would like, such that they're visible to wherever you write your
Application.module()
function. In your test code, you pass in mocks/stubs where appropriate.
πŸ‘ 1
l
Right, that's the path I'd like to take and I think the direction I was heading in thus far if I understand you correctly. For example, in my
Application.module()
I wire up the dependencies and pass them through, i.e.:
Copy code
fun Application.module() {
  val auth0Client = Auth0Client(environment.config)
  val userRepository = UserRepository() // args can be provided here to control the concrete behavior
  userRoutes(auth0Client, userRepository)
  }
I think what I'm missing is how to swap out those dependencies with the ones I'd like to test with. For example, if I have a test:
Copy code
@Test
    fun testRoot() = testApplication {...}
how do I set up the test to use an
Auth0Client
,
UserRepository
, etc. that I'd like to use for testing?
s
This is a very broad subject that'd be hard to cover succinctly in this format, but here are a few notes: β€’ in your tests, you should generally aim to separate unit tests from integration tests. Unit tests are where mocks and stubs come in handy because you're testing a small, exposed segment of code (e.g. a public function on an implementation class) and you want to inspect what the subject under test passes to its dependencies and control the return values that the subject receives from those mocked dependencies β€’ In unit tests, you want to mock interfaces that the dependencies implement as opposed to trying to mock implementing classes β€’ The standard for mocking on the JVM is Mockito, and is probably what I'd recommend as well β€’ In integration tests, you're performing an end-to-end test of code that's been wired up as close to how a real deployment would be wired up without actually calling whatever you can't afford for real (like, for example, a real payment processor if you're testing code that handles credit cards, or a live database to test if your DAO or Repository code is properly converting data objects into valid queries)
l
Right. I guess I am mixing unit with integration testing, which I shouldn't do. Focusing just on unit testing for now, this should be easy enough. Either use interfaces, as you said, or make the constructors my dependency classes take arguments (both are the same basic concept)
Out of curiosity, do you prefer Mockito vs MockK? If so, why?
(I don't have much experience with either)
s
I don't really have any experience with MockK, so I'll leave that open for a subject matter expert to answer
But I do want to stress that you want to use interfaces in either case
l
Just easier/more flexible?
re interfaces
s
Well, it's considered bad form to mock implementing classes
and up until recently, Mockito didn't support mocking final classes (which your implementing classes are by default in Kotlin)
in practice, you don't want to be concerned with the specific behavior of a dependency implementationβ€”what you really want is to see how your test subject interacts with its dependencies and how it handles specific values
the _subject_'s behavior, not its dependencies
l
Right right
I know this is potentially a longer discussion with many different view, but is testing a specific route a unit test or integration test? I would think that once you're at the level of a route, that is closer to integration. Testing frameworks, like for Django, Ktor, etc. tend to push you towards integration tests. For true unit tests, I wouldn't even need
testApplication
since I'd just be testing small public functions and such, right?
s
Well, it dependsβ€”testing a "route" by hitting it with an HTTP client and letting your service complete the whole request as it would in production is definitely an integration test. Testing one route can involve multiple services or controllers as well as caching and the underlying framework managing the various service elements. On the other hand, one route could just mean one call to one controller and end up being not that different from a unit test
l
right
The latter case is rarer in my experience
s
Still, even if all the top level route logic does is call a controller method, it's useful to isolate the controller method from the controller's real dependencies in a unit test
there's value in both is what I mean
l
For sure.
s
Sorry, to answer your original question more directly, you absolutely do not need
testApplication
for a unit testβ€”all you need is the class you're testing and the mocks it needs for instantiation
l
Thanks @Shawn Appreciate all the ideas and feedback. I seem to completely rethink testing every few years and this helped crystallize my approach a lot.
πŸ‘ 1
Unrelated to this discussion, but curious how you organize your tests. Do folks tend to (or is there a convention to) structure the directories and packages in
src/test
to match
src/main
?
s
That's how I've been taught to do it, yeah, and in Java particularly it's helpful to match packages because then you can test package-private code when necessary by placing tests in the same package
l
Right. Never sure which Java things carry over to Kotlin since Kotlin is a lot less restrictive about package names, etc
But I think the organization makes it easier to navigate even if it isn't "needed"
s
I guess the freedom is useful because you may have a need to group classes or files in a way that doesn't necessarily match package structure
l
right
s
a naive example might be the ability to group all your integration tests together while still defining the tests themselves within the packages they're relevant to for access to symbols they wouldn't be able to see otherwise
πŸ‘ 1
l
I was also thinking of using the same package name for corresponding tests but appending
.unit
or
.integration
to split test types. Thoughts on that? Bad idea?
s
I don't quite get what you're envisioning, but usually what I do is if I have a class
Foo
, my unit test is usually called
FooTest
and the integration test
FooIntegrationTest
l
Sorry, let me clarify. I'm asking about (maybe conflating) both directories and package names (realizing that in Kotlin the dir/path does not need to match the package name for a particular class). My
src/main
dir structure looks like this right now:
Copy code
app/src/main/kotlin
β”œβ”€β”€ Application.kt
β”œβ”€β”€ auth0
β”‚   β”œβ”€β”€ Auth0Client.kt
β”‚   β”œβ”€β”€ Auth0UserType.kt
β”‚   └── Token.kt
β”œβ”€β”€ cache
β”‚   └── ResidentCache.kt
β”œβ”€β”€ kafka
β”‚   β”œβ”€β”€ Consumer.kt
β”‚   β”œβ”€β”€ Producer.kt
β”‚   β”œβ”€β”€ Props.kt
β”‚   β”œβ”€β”€ cache
β”‚   β”‚   └── KafkaResidentCache.kt
β”‚   β”œβ”€β”€ model
β”‚   β”‚   β”œβ”€β”€ Resident.kt
β”‚   β”‚   β”œβ”€β”€ ResidentRegisteredEvent.kt
β”‚   β”‚   └── SignupUserData.kt
β”‚   └── user
β”œβ”€β”€ routing
β”‚   β”œβ”€β”€ health
β”‚   β”‚   └── check
β”‚   β”‚       └── CheckHealthRoute.kt
β”‚   └── user
β”‚       β”œβ”€β”€ UserRoutes.kt
β”‚       β”œβ”€β”€ authenticate
β”‚       β”‚   β”œβ”€β”€ A.kt
β”‚       β”‚   └── B.kt
β”‚       β”œβ”€β”€ completeRegistration
β”‚       β”‚   β”œβ”€β”€ C.kt
β”‚       β”‚   └── D.kt
β”‚       β”œβ”€β”€ sendOtpEmail
β”‚       β”‚   β”œβ”€β”€ Foo.kt
β”‚       β”‚   └── Bar.kt
β”‚       β”œβ”€β”€ sendSmsOtp
β”‚       β”‚   β”œβ”€β”€ Foo.kt
β”‚       β”‚   └── Bar.kt
β”‚       └── validateEmailOtp
β”‚           β”œβ”€β”€ Foo.kt
β”‚           └── Bar.kt
└── validation
    β”œβ”€β”€ Extensions.kt
    └── RequestValidations.kt
I have followed the convention here https://kotlinlang.org/docs/coding-conventions.html#directory-structure, i.e. omitting the common root where my root package is
org.wol.useridentityservice
. For example, the src files in the
auth0
directory have package
org.wol.useridentityservice.auth0
and so on. Now I get to my
test
directory. Should the directory structure match
main
so that auth0 tests are in
auth0
directy and the package would also be
org.wol.useridentityservice.auth0
?
Seems like it's as simple as matching the dir structure of
main
and using the same package name, like so
πŸ‘† 1
πŸ‘ 1
and then using
TokenIntegrationTest
to distinguish, as you suggest
That's a lot simpler than what I envisioned. I like it
s
good stuff!
l
Thanks again, appreciate it
πŸ‘ 1
Having some trouble figuring out how to swap certain dependencies for test implementations for the purpose of integration testing. Specifically, my application uses
EngineMain
to start a server and in my
Application.module()
I set up some dependencies, such as an Auth0 client, kafka consumer, etc.
Copy code
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)

fun Application.module() {
  val auth0Client = Auth0Client(environment.config)
  val residentCache = KafkaResidentCache()
  userRoutes(auth0Client, residentCache)
}
I'd like to write an integration test for my routes without relying on a live kafka broker or making requests to Auth0 (obviously). I was hoping I could have default dependencies that get passed all the way up from
main
over to the
Application.module()
, but I'm not sure if that's possible. I've seen some examples that seem a bit more flexible (e.g. this example where they have
fun Application.main(httpClient: HttpClient = applicationHttpClient)
which makes it easy to swap a client when testing), but still unclear what exactly this would look like in my case. Is what I'm aiming for even the right goal? Should I instead be aiming to have an embedded Kafka (a la https://kotest.io/docs/extensions/embedded-kafka.html) or running Kafka in Docker so that the integration tests resemble the production scenario as close as is feasible?
I'll be damned, this actually works...I think 🀣
Copy code
fun Application.module(auth0Client: Auth0Client = Auth0Client(environment.config),
                       residentCache: KafkaResidentCache = KafkaResidentCache()) {
Not sure why I assumed that wouldn't...