-
-
Notifications
You must be signed in to change notification settings - Fork 55
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
Final classes vs mocking #52
Comments
Interesting - I had not thought about this and have never seen it mentioned in any of the design discussions about class modifiers. Interestingly they are positioned very much too help ensure consistent behavior for packages! I'm still learning about the benefits, so let me think a bit about whether and where these modifiers are really needed. |
Sadly the only way would be to provide test/stub classes beside the real ones. In |
Perhaps the easiest is to not make the FileDownloader class final. The final modifier is more important for classes that are inputs in the FileDownloader class (such as Task). When you mock FileDownloader, does that in itself trigger the warnings for Database, TaskStatusUpdate and Batch, or do those only appear when you mock those classes? And why would you want to mock classes like Batch and TaskStatusUpdate? |
No they are automatically mocked. I just mock the |
Just to be exact, I'm doing this with mockito: @GenerateNiceMocks([
MockSpec<FileDownloader>(),
]) And this triggers the errors for class _FakeBatch_27 extends _i1.SmartFake implements _i15.Batch {
_FakeBatch_27(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
} Then for returnValue: _i5.Future<_i15.Batch>.value(_FakeBatch_27(
... This is the default behavior. I think it is possible to define custom default returns with |
Understand. That's going to be a problem though because I really don't want to open up all those classes - it's kinda the point of the class modifiers, especially for packages, as it makes future changes in the implementation much easier. |
Sadly thats the only option if you want to keep the |
Yes, if you use it for testing then a stub isn't going to be good enough. The 'proper' alternative I think is for me to define the FileDownloader interface as a separate It requires two things:
On naming, which do you think is better: Let me know what you think. I'm not able to do actual work on this for another ~2 weeks, but if you have time you could experiment with a fork (and I'd be happy to merge it in if it works). |
Sorry, one more thought (I'm learning about these new modifiers myself!): It looks like Mockito implements a class interface, not extend it. So can you check if mocking works if you change the modifier of FileDownloader to 'interface'? The main thing I'm trying to avoid is someone extending FileDownloader as that is likely to lead to problems down the road. Implementing requires a new implementation (like a mock) and is not an issue. This would accomplish the same as the above without the need to introduce a separate interface class (API) or a change to the type of the variable you inject, so may solve all problems. |
HI, I found a few minutes and changed the I got mock working using a simple |
I had time now to try it out and all of my tests are green with it, thanks. Nice solution, when the class is marked with interface then it cannot be extended, so you also achieve the main goal. |
Implemented in V7.0.2 |
I had a discussion about this topic on flutter discord and the conclusion was that the recommended approach is to wrap plugin classes with selfmade wrapper classes on app side and mock those: https://docs.flutter.dev/packages-and-plugins/plugins-in-tests So my approach was fragile, to just use |
Interesting, thanks for sharing that link. It's quite a big ask to expect developers to wrap every plugin though - I understand the benefit (and in a way I've done that within the plugin with the Database class which wraps the Localstore package) but I also believe one should be able to just use a plugin like background_downloader directly. Allowing developers to mock the class by making the interface available (as I've done now, in response to this issue) therefore feels appropriate to me. Package developers should mostly care about avoiding extension of classes that are inputs to their plugin (like Task) and care less about classes that are outputs (like TaskStatusUpdate), because if a developer implements those it doesn't affect the plugin operation. A non-breaking plugin API change then may indeed require rebuilding the Mock, but nothing else, so I'm ok with that (and won't consider interface changes a breaking change if a normal application doesn't break - for example if I add a method to FileDownloader that a developer can choose to use or ignore). One could argue that any interface change is breaking because a developer implementing it will need to implement those changes, but that was true with Dart 2 and never considered a reason for a major version change, so I'm sticking with that policy 😃 |
fyi a discussion has risen from the above mentioned discord thread: dart-lang/language#3106 |
Hello,
When writing tests which uses Filedownloader I always want to use mocks instead of the real class, as I don't want to download anything. But now on 7.0+ you marked many classes with final and mockito cannot generate mocks from them anymore sadly:
Are you sure those finals are really required?
Build runner complaining for these currently:
Database
TaskStatusUpdate
Batch
I created an issue for mockito about this: dart-lang/mockito#635
The text was updated successfully, but these errors were encountered: