Skip to content

fix: resolve @InjectMockKs initialization order based on dependencies#1500

Merged
Raibaz merged 4 commits intomockk:masterfrom
neungs-2:master
Jan 21, 2026
Merged

fix: resolve @InjectMockKs initialization order based on dependencies#1500
Raibaz merged 4 commits intomockk:masterfrom
neungs-2:master

Conversation

@neungs-2
Copy link
Contributor

Summary

  • Fix @InjectMockKs failing when dependency order differs from field declaration order
  • Use topological sort (Kahn's algorithm) to resolve correct initialization order
  • Add circular dependency detection with clear error message
  • Add comprehensive dependency-order tests for @InjectMockKs

Problem

When multiple @InjectMockKs fields have dependencies on each other, MockK processed them in reflection order (roughly alphabetical), causing "No matching constructors found" errors.

  class ServiceB(val repository: Repository)
  class ServiceA(val b: ServiceB)  // A depends on B

  class MyTest {
      @InjectMockKs lateinit var a: ServiceA  // 'a' < 'b' alphabetically
      @InjectMockKs lateinit var b: ServiceB  // but B must be created first
  }

Solution

Analyze constructor parameter types to build a dependency graph, then use topological sort to determine correct initialization order.

Changes

  • modules/mockk/src/jvmMain/kotlin/io/mockk/impl/annotations/JvmMockInitializer.kt
    • Add dependency-aware ordering for @InjectMockKs via:
      • sortByDependencyOrder(...)
      • buildDependencyGraph(...)
      • topologicalSort(...) (Kahn's algorithm + circular detection)
  • modules/mockk/src/jvmMain/kotlin/io/mockk/impl/annotations/InjectionHelpers.kt
    • Add reflection helpers:
      • KProperty<*>.getReturnTypeKClass()
      • KClass<*>.getConstructorParameterTypes()
      • KType.getKClass()
  • modules/mockk/src/commonTest/kotlin/io/mockk/it/InjectMocksTest.kt
    • Add InjectMocksDependencyOrderTest with cases:
      • twoLevelDependency()
      • threeLevelChainDependency()
      • diamondDependency()
      • misleadingNamesDependency()
      • circularDependencyDetection()

Test

  • Two-level dependency (A → B)
  • Three-level chain (A → B → C)
  • Diamond dependency (D → B,C → A)
  • Misleading field names
  • Circular dependency detection

Closes #1496

@Raibaz
Copy link
Collaborator

Raibaz commented Jan 15, 2026

Thanks a lot for putting this together!

I'm a bit worried about the performances, though.

Would it make sense to make the topological sorting of dependencies opt-in and not enabled by default, for instance by adding a flag to @InjectMockKs?

Do you have any data about the impact of topological sorting on the performances?

@neungs-2
Copy link
Contributor Author

I also wanted to ask you if the flag method would be good, but you just pointed it out!
I'll add the flag and do the performance test.

@neungs-2
Copy link
Contributor Author

I modified the dependency sorting to be applied via a flag.
This commit failed the ConnectedCheck [api=21] test, so I'll fix that.

For now, I'll add the changes and performance test results.

Changes

  • Added an optional dependency‑ordered initialization for @InjectMockKs using a topological sort. The previous reflection‑order behavior is preserved when the flag is off.
    • Core implementation: modules/mockk/src/jvmMain/kotlin/io/mockk/impl/annotations/JvmMockInitializer.kt
  • Exposed a new flag useDependencyOrder on MockKAnnotations.init() (default false).
    • API entry: modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt
    • DSL wiring: modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt, modules/mockk-dsl/src/commonMain/kotlin/io/mockk/MockKGateway.kt
    • Updated public API snapshots: modules/mockk/api/mockk.api, modules/mockk-dsl/api/mockk-dsl.api

How to enable the flag

  • MockKAnnotations.init(..., useDependencyOrder = true)

Performance impact of DependencyOrder in Mock initialization

I ran JMH benchmarks comparing initWithDependencyOrder vs initWithoutDependencyOrder across different dependency graph shapes (independent, wide, linear, diamond) and sizes.

Focusing on the more stable and realistic case (size = 20, thrpt mode):
• independent: ~5.3% slower with dependency order
• wide: ~2.9% slower with dependency order
• linear: ~3.9% slower with dependency order
• diamond: ~5.4% slower with dependency order

Overall, applying DependencyOrder consistently introduces a 3~5% throughput regression compared to the unordered version. Therefore, applying the flag seems appropriate.

main summary

Benchmark Variant Shape Size Score (ops/s) Units
initWithDependencyOrder independent 5 9,625.818 ops/s
initWithDependencyOrder independent 20 4,775.431 ops/s
initWithDependencyOrder wide 5 9,533.150 ops/s
initWithDependencyOrder wide 20 2,574.726 ops/s
initWithDependencyOrder linear 5 10,179.793 ops/s
initWithDependencyOrder linear 20 2,679.349 ops/s
initWithDependencyOrder diamond 5 9,670.743 ops/s
initWithDependencyOrder diamond 20 2,595.724 ops/s
initWithoutDependencyOrder independent 5 10,220.903 ops/s
initWithoutDependencyOrder independent 20 5,030.059 ops/s
initWithoutDependencyOrder wide 5 10,223.527 ops/s
initWithoutDependencyOrder wide 20 2,649.183 ops/s
initWithoutDependencyOrder linear 5 10,420.318 ops/s
initWithoutDependencyOrder linear 20 2,783.040 ops/s
initWithoutDependencyOrder diamond 5 10,244.578 ops/s
initWithoutDependencyOrder diamond 20 2,735.884 ops/s

@Raibaz
Copy link
Collaborator

Raibaz commented Jan 19, 2026

Thanks a lot for looking into the performances of this change! 🙏

When we release it, let's make sure to document that switching on the useDependencyOrder flag will result in a 3-5% performance hit.

@Raibaz
Copy link
Collaborator

Raibaz commented Jan 20, 2026

@neungs-2 this looks good to me.

Can you please add documentation for it in README.md so that we can merge it?

Thanks!

@neungs-2
Copy link
Contributor Author

@Raibaz Thank you for the quick review!

I've added the @InjectMockKs dependency order section to README.md.
Please feel free to suggest any changes or improvements.

@Raibaz Raibaz merged commit cd84546 into mockk:master Jan 21, 2026
23 checks passed
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

Successfully merging this pull request may close these issues.

Dependency of one InjectMockKs on another using lateinit var and constructor injection

2 participants