Closed Andre-Coelhoo closed 3 months ago
AFAIK the method channels methods need to be overridden in tests when working with plugins. I wanted to post an issue, asking about how to go about testing with Mobile Scanner, maybe an elaboration on which methods to override safely etc. But seeing the many issues I don't know if the author or maintainers will get to them in time
AFAIK the method channels methods need to be overridden in tests when working with plugins. I wanted to post an issue, asking about how to go about testing with Mobile Scanner, maybe an elaboration on which methods to override safely etc. But seeing the many issues I don't know if the author or maintainers will get to them in time
but how do I override the function of a package?
You indeed need to use https://api.flutter.dev/flutter/flutter_test/TestDefaultBinaryMessenger/setMockMethodCallHandler.html to mock method calls in tests
Before I forget, there is also https://docs.flutter.dev/testing/plugins-in-tests which explains the setup, and why it fails in your case.
I am planning on rewriting part of the plugin to use the plugin_platform_interface
, as that is the way to develop plugins today. This will give you the option to register a mock implementation of the platform interface (see the doc link above)
Hello again, I'm trying to use setMockMethodCallHandler. I made this function that runs before the test
void mockMobileScanner() {
const channel = MethodChannel('mobile_scanner/mobile_scanner.dart');
handler(MethodCall methodCall) async {
if (methodCall.method == '_startScanner') {
return null;
}
return null;
}
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
but still got this error: The following _CastError was thrown running a test (but after the test had completed): type 'MissingPluginException' is not a subtype of type 'MobileScannerException' in type cast
When the exception was thrown, this was the stack:
@Andre-Coelhoo You aren't mocking the right channel.
You should update the name of the channel to match, so
void mockMobileScanner() {
const channel = MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');
handler(MethodCall methodCall) async {
if (methodCall.method == '_startScanner') {
return null;
}
return null;
}
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
@juliansteenbakker We should really be marking the Method/Event Channels as @visibleForTesting
and making them public.
Then people don't have to rely on guessing the names of the channels for their tests.
Sorry to keep you busy but I get this exception. it gives me the impression that it can't find the method to override
The following MissingPluginException was thrown while de-activating platform stream on channel dev.steenbakker.mobile_scanner/scanner/event: MissingPluginException(No implementation found for method cancel on channel dev.steenbakker.mobile_scanner/scanner/event)
When the exception was thrown, this was the stack:
@Andre-Coelhoo For event channels there is https://api.flutter.dev/flutter/flutter_test/TestDefaultBinaryMessenger/setMockStreamHandler.html
@Andre-Coelhoo You aren't mocking the right channel.
You should update the name of the channel to match, so
void mockMobileScanner() { const channel = MethodChannel('dev.steenbakker.mobile_scanner/scanner/method'); handler(MethodCall methodCall) async { if (methodCall.method == '_startScanner') { return null; } return null; } TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger .setMockMethodCallHandler(channel, handler); }
@juliansteenbakker We should really be marking the Method/Event Channels as
@visibleForTesting
and making them public. Then people don't have to rely on guessing the names of the channels for their tests.
Thanks for the sample. The MissingPluginException is gone.
But if I use this mock I get: "mobile_scanner: MobileScannerException: permissionDenied"
Any ideas for this?
@navaronbracke Can we get some simple test examples for the MethodChannel and EventChannel implementations?
@RahmiTufanoglu Sure!
So you have to use https://api.flutter.dev/flutter/flutter_test/TestDefaultBinaryMessenger/setMockMethodCallHandler.html or https://api.flutter.dev/flutter/flutter_test/TestDefaultBinaryMessenger/setMockStreamHandler.html
depending on whether it is a MethodChannel or an EventChannel.
For example for a MethodChannel:
// 'channel' is the MethodChannel to mock
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) {
// ...
}
);
or an EventChannel:
// 'channel' is the EventChannel to mock
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
channel,
MockStreamHandler.inline(
onListen: (Object? arguments, MockStreamHandlerEventSink events) {
// ...
},
onCancel: (Object? arguments) {
// ...
},
),
);
The code sample assumes that the TestDefaultBinaryMessengerBinding.instance is non-null (which is not the case in older Flutter versions)
Note: Since mobile_scanner
does not yet provide the channels as @visibleForTesting
you have to define them manually for now. (The names in the channels should match what is used internally) I plan on fixing this soon, though.
@navaronbracke Nice, I did something similar.
var canceled = false;
const channel = EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
channel,
MockStreamHandler.inline(
onListen: (arguments, events) {
events
..success('42')
..endOfStream();
},
onCancel: (arguments) {
canceled = true;
},
),
);
final events = await channel.receiveBroadcastStream().toList();
expect(events, orderedEquals(['42']));
You do not give it an argument. channel.receiveBroadcastStream()
returns the event stream from the underlying event sink. So you get all the events from that event stream anyway. IIRC these events are the scanned code events.
@navaronbracke
I have the following scenario:
When a widget/page is opened, an onBarcodeDetected method is called. As soon as the device is able to scan something, it navigates to the next page. How can I test the onDetect method of the MobileScanner to see if it returns a valid value?
The onDetect
returns a BarcodeCapture intance.
You could test if that object contains a valid barcode in the barcodes
property.
@navaronbracke
I hope I'm not stealing too much of your time. Do you have a simple example?
I don't have a working example up-front, but I can give some pointers.
MobileScannerController
. I.e. request
, state
and the event channel as well. Why do we mock this? Because we cannot use a real device to point at a real code during the test :)MobileScanner
widget, passing in the onDetect
function, using the WidgetTesterMobileScannerController
is started (you might need to start it manually before pumping the widget)onDetect
, capture the 'barcode'expect()
Bear in mind, I have not tested this flow. The key point is that the camera cannot really be used to scan the barcode during a test (there is nobody holding the barcode in front of the device, so to speak) . Therefor you provide mocks for that part of the test
I do plan on writing tests for mobile_scanner
itself, that verify this behavior, but that is on my TODO list down the line.
I try to create an example. Maybe I can contribute
Hello everyone, I doing golden tests and Im getting multiple errors, this is the widget: (im using the version 3.0.0) `class ScanWidget extends StatelessWidget { const ScanWidget({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { final scanRectSize = MediaQuery.of(context).size.width * 0.6;
} } `
this is my golden test `void main() { final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
group('scan tagOnboarding screen Golden Test', () { for (GoldenTheme theme in GoldenTheme.values) { for (GoldenScale scale in GoldenScale.values) { for (PhysicalSize physicalSizeDevices in PhysicalSize.values) { testWidgets('scan tagOnboarding page', (WidgetTester tester) async { // change screen size and pixel density ratio binding.window.physicalSizeTestValue = physicalSizeDevices.physicalSizes; binding.window.devicePixelRatioTestValue = 1.0;
}); } `
I get this errors:
`The following _CastError was thrown running a test: type 'MissingPluginException' is not a subtype of type 'MobileScannerException' in type cast
When the exception was thrown, this was the stack:
0 _MobileScannerState._startScanner.. (package:mobile_scanner/src/mobile_scanner.dart:138:35)
1 State.setState (package:flutter/src/widgets/framework.dart:1133:30)
2 _MobileScannerState._startScanner. (package:mobile_scanner/src/mobile_scanner.dart:137:9)