-
-
Notifications
You must be signed in to change notification settings - Fork 380
[#1304] feat: Restrict mocking of certain classes and add configuration option #1340
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
[#1304] feat: Restrict mocking of certain classes and add configuration option #1340
Conversation
- Implement `RestrictedMockClasses` to maintain a list of restricted classes - Use `isAssignableFrom` to check for restricted class types dynamically - Allow adding and removing user-defined restricted types - Include default restricted types: System, Collections, I/O classes
- Added `disallowMockingRestrictedClasses` property to `MockKSettings` - Loaded setting from `settings.properties` for better configurability - Integrated setting into `RestrictedMockClasses` to prevent restricted class mocking - If enabled, throws `IllegalArgumentException` instead of just logging a warning
- Added a test to verify that attempting to mock a user-defined restricted class throws an `IllegalArgumentException` when `disallowMockingRestrictedClasses` is enabled. - Ensures that dynamically added restricted classes are properly handled. - Validates that the correct exception message is returned.
Thanks for looking into this! Can you please run ./gradlew :mockk-dsl:apiDump and add the api dump to the PR? |
userDefinedRestrictedTypes.any { it.isAssignableFrom(clazz) } | ||
} | ||
|
||
fun addRestrictedType(clazz: Class<*>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the use case for changing the restricted types in code?
I'm worried that if we allow adding or removing a restricted type in code, tests will no longer be atomic.
Think of this case:
Test A mocks class Foo
Test B adds class Foo to the restricted types
Now the order of execution matters, because executing A before B passes and executing B before A doesn't, and yet tests should be independent.
How about making the user defined restricted types a configuration property?
Hi @Raibaz , To guarantee test independence, I implemented MockingRestrictedExtension by extending BeforeEachCallback. This ensures that each test runs with a clean state. Additionally, for better usability, I modified the API to allow users to:
Thanks |
Thanks for looking into this! I have a few concerns about this approach, though:
How about adding a configuration property in That would allow you to add, in your
I know it's more error-prone than annotating restricted classes, but I think it gives the right amount of flexibility. WDYT? |
@Raibaz Thanks for your feedback! Currently, when Given this behavior, I’d like to clarify the following regarding the mockk.properties configuration:
Looking forward to your insights! 😊 |
Oh, I see, that makes sense, thanks for clarifying! So, I think that if a user restricts mocking a class in a MockKSetting, by default trying to mock it should throw an exception, or log a warning, anywhere in the application, without having to explicitly add the @MockingRestrictedExtension. If anything, I'd like it to work the other way around: if class What do you think about this approach? |
(also, thanks for the followup!) |
That makes a lot of sense! I see the value in making the restriction the default behavior while allowing an explicit override when absolutely necessary. Instead of relying solely on annotations, I’ll explore the possibility of managing restricted classes globally through a configuration file (e.g., mockk.properties). This would provide more flexibility while ensuring consistency across all tests. I'll look into how this can be implemented effectively and update you with my findings. |
- Introduced `MockkValidator` to enforce global mock restrictions. - Automatically throws `MockKException` or logs a warning if a restricted class is mocked. - Updated `RestrictMockkConfiguration` to support user-defined restricted classes. - Added support for hierarchical type checking (e.g., restricting `Collection` applies to `ArrayList`). - Implemented `TestPropertiesLoader` for dynamic test configurations. - Refactored tests: separated collection-related tests into `RestrictMockkCollectionTest`.
@Raibaz |
I like this! I think we can merge this, but it needs two minor improvements:
Thanks a lot for looking into this! <3 |
I've completed adding documentation on how to use mocking restrictions in I'm really happy to contribute to a project I use frequently, and I appreciate all the valuable feedback you've provided. 😊 |
README.md
Outdated
@Test | ||
fun `when throwExceptionOnBadMock is true should throw MockKException for collections`() { | ||
assertThrows<MockKException> { | ||
mockk<List<String>>() // Throws MockException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: should be // Throws MockKException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve updated the comment to MockException -> MockKException
as suggested. 😭
Thanks a lot for the contribution and for the continued follow ups! |
Update mockk from 1.13.13 to 1.13.17 <details> <summary>mockk/mockk (io.mockk:mockk)</summary> ### p`v1.13.17`](https://redirect.github.com/mockk/mockk/releases/tag/1.13.17) [Compare Source](mockk/mockk@1.13.16...1.13.17) ##### What's Changed - Fix(Issue #1333): Bug fix for the issue with MockK 1.13.16 Wraps Results Objects Twice by @kpadhiamex in [#1334](mockk/mockk#1334) - Fix (issue# 1329) parallel testing for unmockkAll by @kpadhiamex in [#1335](mockk/mockk#1335) - [[#1304]](mockk/mockk#1304) feat: Restrict mocking of certain classes and add configuration option by @devtaebong in [#1340](mockk/mockk#1340 - Update README.md - Clarify that private fields cannot be mocked by @p4ulor in [#1347](mockk/mockk#1347) - Added new property "failOnSetBackingFieldException" to fail test if a backing field could not be set by @cgm-aw in [#1349](mockk/mockk#1349) - Fix compilation error in constructedWith docs by @TWiStErRob in [#1354](mockk/mockk#1354) **Full Changelog: **Full Changelog**: mockk/mockk@1.13.16...1.13.17 ### [`v1.13.16`](https://redirect.github.com/mockk/mockk/releases/tag/1.13.16) [Compare Source](https://redirect.github.com/mockk/mockk/compare/1.13.14...1.13.16) ##### What's Changed - Fix( Issue [#​1073](https://redirect.github.com/mockk/mockk/issues/1073)): Bug fix for the issue with mocking value classes with coEvery by [@​kpadhiamex](https://redirect.github.com/kpadhiamex) in [https://github.com/mockk/mockk/pull/1332](https://redirect.github.com/mockk/mockk/pull/1332) **Full Changelog**: mockk/mockk@1.13.14...1.13.16 ### [`v1.13.14`](https://redirect.github.com/mockk/mockk/releases/tag/1.13.14) [Compare Source](https://redirect.github.com/mockk/mockk/compare/1.13.13...1.13.14) ##### What's Changed - fix(1308): Handle nullable complex and nested value classes by [@​VasilisDrettas-tomtom](https://redirect.github.com/VasilisDrettas-tomtom) in [https://github.com/mockk/mockk/pull/1314](https://redirect.github.com/mockk/mockk/pull/1314) - Fix(Issue no. 1330) for Relaxed Mocking Value When Property is Nested Value Class by [@​kpadhiamex](https://redirect.github.com/kpadhiamex) in [https://github.com/mockk/mockk/pull/1331](https://redirect.github.com/mockk/mockk/pull/1331) ##### New Contributors - [@​kpadhiamex](https://redirect.github.com/kpadhiamex) made their first contribution in [https://github.com/mockk/mockk/pull/1331](https://redirect.github.com/mockk/mockk/pull/1331) **Full Changelog**: mockk/mockk@1.13.13...1.13.14 </details>
@devtaebong thanks for this feature 🙏 What would you recommend for this kind of scenario?
|
@SimonMarquis Glad to hear you’re making good use of the feature! mockk.properties is designed to be defined explicitly within each module. So in multi-module projects, you’ll need to place the file under each module’s test resources if you want module-specific configurations. That said, it might be possible to set common defaults across modules using JVM system properties (e.g., via -D options). This could be a more scalable approach for large projects, but I’m not 100% sure if all settings from mockk.properties are respected via system properties — it would need some verification. |
@devtaebong Thank you so much for this work! I've tested it as well on multiple modules, and it looks like you can actually centralize the mockk properties file! Some things I've observed while applying this change on a projects with big files: Lots of tests are failing now since naturally, there are some tests that mock a collection for example. Would you consider to introduce an option to gradually increase the restricted classes as opposed to starting with a full list? For example, the idea is that for big orgs, we would start with no restrictions, and then over time, we can start adding collection, Map, etc. The current state is that there are a lot of tests that will fail, so this feature would need to be all/ or nothing if you have failing tests. Another nice thing, is it would be really helpful and good QoL change to be able to isolate this feature for specific tests i.e Robolectric vs. pure JVM tests! Let me know what you think, thank you again, really great work! |
Could you share, @nishatoma, how you centralized the |
Summary
This PR introduces a mechanism to prevent mocking of certain classes that are generally considered bad practice.
By default, attempting to mock restricted classes will log a warning. Additionally, a new configuration option has
been added to allow users to enforce strict restrictions, causing an exception to be thrown when attempting to mock
a restricted class.
Changes
1️⃣ Restricted Mocking Mechanism
RestrictedMockClasses
to maintain a list of non-mockable classes.2️⃣ New Configuration Option
MockKSettings.disallowMockingRestrictedClasses
to enforce strict mocking prevention.settings.properties
:disallowMockingRestrictedClasses=false
3️⃣ Configurable Restricted Class List
4️⃣ Integration with mockk()
mockk<T>()
now checks for restricted classes before creating a mock.5️⃣ Unit Tests