Skip to content

Fix type matching for any() when parameter is Any (#1296)#1494

Merged
Raibaz merged 7 commits intomockk:masterfrom
chapakook:fix/1296-any-type-matcher
Jan 12, 2026
Merged

Fix type matching for any() when parameter is Any (#1296)#1494
Raibaz merged 7 commits intomockk:masterfrom
chapakook:fix/1296-any-type-matcher

Conversation

@chapakook
Copy link
Contributor

Fixes: #1296

Problem

The any<T>() and any(KClass) matchers do not respect the requested type when
a mocked method parameter is declared as Any.

As a result, verifications such as any<Int>() may incorrectly match arguments
of a different runtime type (e.g. String), leading to false-positive matches
during verification.

Solution

Introduce a type-aware matcher (AnyTypedMatcher) for the any family of
matchers and apply type validation during invocation matching.

The solution:

  • Preserves the type information provided to any<T>() and any(KClass)
  • Ensures type checks are applied only when the runtime argument is non-null
  • Supports Kotlin value classes whose runtime representation may differ from
    their declared type
  • Keeps existing matcher behavior unchanged to avoid breaking compatibility

This fixes the incorrect matching behavior reported in #1296 while maintaining
existing semantics for other matchers.

Changes

  • Introduced AnyTypedMatcher to carry and validate type information for
    any<T>() and any(KClass)
  • Refined InvocationMatcher to invoke TypedMatcher.checkType only for
    non-null arguments
  • Added value-class–aware type handling scoped specifically to AnyTypedMatcher
  • Added regression tests covering:
    • Incorrect matches for any<T>() and any(KClass) with Any-typed parameters
    • Correct matching for valid types
    • Comparison with capture behavior to ensure type safety

Tests

Added AnyMatcherTypeTest to verify:

  • any<Int>() and any(Int::class) do not match String arguments
  • Correct matches for valid argument types
  • Proper behavior when using any(Any::class)
  • Consistency with capture-based type checks

Note

Type-checking behavior for matchers other than any() remains unchanged.
Additional handling for Kotlin value classes is intentionally scoped to
AnyTypedMatcher to minimize side effects and preserve backward compatibility.

Introduce AnyTypedMatcher to ensure the any matcher correctly preserves type information.
Refine the conditions for invoking TypedMatcher.checkType in InvocationMatcher
to clarify null handling and type validation.
Add test cases to verify the related behavior and improve stability.

- Enforce strict type matching for any<T>() and any(KClass)
- Support various type representations such as value classes
- Strengthen type safety while maintaining backward compatibility
- Add new tests to ensure reliability of the changes
Add tests to verify that any matchers behave correctly with proper type handling.
- Validate type matching when using any<Int>() and any(Any::class)
- Improve code reliability and test coverage
Remove unnecessary blank lines to improve code readability.
Clean up the test code to keep its structure clear and consistent.
@chapakook chapakook marked this pull request as ready for review January 9, 2026 11:24
Split the constructor declaration of AnyTypedMatcher into multiple lines
to improve readability.
Align the class inheritance section by placing each interface on a separate line
for better code consistency.
Make the code easier to maintain and review.
@chapakook chapakook marked this pull request as draft January 9, 2026 11:45
@chapakook
Copy link
Contributor Author

chapakook commented Jan 9, 2026

What I changed

I updated the behavior of any() / any(KClass) matchers to improve type checking
when value classes and their boxed / underlying representations are involved.

Specifically:

  • Added additional normalization logic in AnyTypedMatcher.checkType
  • Extended matching to cover:
    • value class instances
    • boxed types
    • underlying boxed types inferred from value class constructors

The goal is to make any<T>() behave consistently when value classes are used
as arguments.

Why this change is needed

With value classes, any<T>() may currently fail type checks depending on
how the argument is boxed at runtime.
This causes unexpected mismatches even though the argument is logically compatible.

This change attempts to make the matcher more permissive and predictable
in those scenarios.

Current issue / question

During this change, AnyTypedMatcher became part of the public API of mockk-dsl,
which causes jvmApiCheck to fail.

Before proceeding further, I would like to confirm:

  • Is it acceptable for AnyTypedMatcher to be public in mockk-dsl?
  • Or should this matcher be kept internal / relocated to avoid an API surface change?

I'm happy to adjust the implementation based on your preferred direction.

@Raibaz
Copy link
Collaborator

Raibaz commented Jan 9, 2026

If it's a new matcher type, it's correct for it to be included in the public API.

You should be able to make the build pass by running ./gradlew apiDump.

Add the AnyTypedMatcher class to enable matching of arguments with various types.
Implement the EquivalentMatcher, Matcher, and TypedMatcher interfaces
to ensure consistent behavior.
Include methods for type checking, copying, and substitution
to allow more flexible test definitions.
@chapakook
Copy link
Contributor Author

Thanks! 😄 Confirmed it's a new matcher type.
I ran ./gradlew apiDump and committed the updated API snapshot (mockk-dsl.api).

@chapakook chapakook marked this pull request as ready for review January 10, 2026 03:16
@Raibaz
Copy link
Collaborator

Raibaz commented Jan 11, 2026

Thanks a lot for looking into this!

Would you also please add documentation about the new matcher type here?

Normalize markdown formatting in the README and fix typos.
- Unify spacing and line breaks in tables, code blocks, and lists
- Correct typos in several descriptions
- Remove unnecessary blank lines to improve readability

Improve overall consistency and readability of the documentation.
@chapakook
Copy link
Contributor Author

😄 Thanks for the suggestion.
I’ve added documentation for the new matcher type.
Please let me know if anything else should be clarified.

README.md Outdated
|---------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| `any()` | matches any argument |
| `any(Class)` | matches any argument of the give Class (for reflective mocking) |
| `any()` | matches any argument of type `T` (type-checked) |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be clearer if the matcher was described as any<T>(), since you then refer to type T in the description?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out 👍😄 that makes sense.
Using any<T>() does read clearer given that the description refers to type T.
I’ll update the README to reflect this.

Update the documentation to describe using `any<T>()` for explicitly specifying the argument type.
This improves clarity around type-checked matchers.
@Raibaz Raibaz merged commit 2c8db98 into mockk:master Jan 12, 2026
20 checks passed
@Raibaz
Copy link
Collaborator

Raibaz commented Jan 12, 2026

Thanks for following up on this! 🙏

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.

any<SpecificType>() matcher ignores the concrete type and matches all types

2 participants