How do I stub a function that does not belong to a class, during a widget test? - testing

I am creating a flutter app that uses the native camera to take a photo, using the official flutter camera package (https://pub.dev/packages/camera). The app opens up a modal that loads a CameraPreview based on the the result of the availableCameras function from the package and a FloatingActionButton which takes a photo when pressed. While creating a widget test for this modal, I can not figure out how to stub the availableCameras function to return what I want during tests.
I tried using the Mockito testing package, but this only supports mocking classes. Since this function does not belong to a class, I cannot mock it.
The availableCameras function returns a list of cameras that the device has. I want to be able to control what comes back from this function, so that I may test how my widget reacts to different cameras. What is the proper way to have this function return what I want during a widget test?

Mockito can mock functions too. In dart, functions are classes with a call method.
You can, therefore, use Mockito as usual, with an abstract call method:
class MockFunction extends Mock {
int call(String param);
}
This example represents a int Function(String param).
Which means you can then do:
final int Function(String) myFn = MockFunction();
when(myFn('hello world')).thenReturn(42);
expect(myFn('hello world'), equals(42));

In this very specific situation, you can mock the method channel call handler.
const cameraMethodChannel = MethodChannel('plugins.flutter.io/camera');
setUpAll(() {
cameraMethodChannel.setMockMethodCallHandler(cameraCallHandler);
});
tearDownAll(() {
cameraMethodChannel.setMockMethodCallHandler(null);
});
Future<dynamic> cameraCallHandler(MethodCall methodCall) async {
if (methodCall.method == 'availableCameras') return yourListOfCameras;
}

Remi's answer here is correct. Here is a more complete recent example that returns a future. This article explains how you can build the widget test around your existing code. this uses mocktail.
import 'package:gistfile/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
class LaunchMock extends Mock {
Future<bool> call(
Uri url, {
LaunchMode? mode,
WebViewConfiguration? webViewConfiguration,
String? webOnlyWindowName,
});
}
void main() {
testWidgets('Test Url Launch', (tester) async {
//These allow default values
registerFallbackValue(LaunchMode.platformDefault);
registerFallbackValue(const WebViewConfiguration());
//Create the mock
final mock = LaunchMock();
when(() => mock(
flutterDevUri,
mode: any(named: 'mode'),
webViewConfiguration: any(named: 'webViewConfiguration'),
webOnlyWindowName: any(named: 'webOnlyWindowName'),
)).thenAnswer((_) async => true);
final builder = compose()
//Replace the launch function with a mock
..addSingletonService<LaunchUrl>(mock);
await tester.pumpWidget(
builder.toContainer()<MyApp>(),
);
//Tap the icon
await tester.tap(
find.byIcon(Icons.favorite),
);
await tester.pumpAndSettle();
verify(() => mock(flutterDevUri)).called(1);
});
}

Related

How to mock a named import object type const in react native using jest?

Hii everyone currently I'm using the following library
https://www.npmjs.com/package/react-native-biometrics
for my project but I stuck here when I'm trying to write the unit tests for it I have mocked the library like below
import ReactNativeBiometrics, { BiometryTypes } from 'react-native-biometrics';
jest.mock ('react-native-biometrics', () =>
{
class ReactNativeBiometrics {
constructor(){
}
isSensorAvailable() {
}
}
return ReactNativeBiometrics;
});
Now I'm trying to mock
BiometryTypes
but its not getting mocked anyone can give some idea how this can be mocked.
BiometryTypes is exported like below
export declare const BiometryTypes: {
TouchID: string;
FaceID: string;
Biometrics: string;
};
I tried like below but its not working
import { BiometryTypes } from 'react-native-biometrics';
import * as all from 'react-native-biometrics';
const TouchID = 'TouchID' ;
all.BiometryTypes = {
TouchID: TouchID,
FaceID: TouchID
};
You can combine named and default mocks in factory function:
jest.mock ('react-native-biometrics', () =>
{
class ReactNativeBiometrics {
......
}
return {
__esModule: true
default: ReactNativeBiometrics,
BiometryTypes: { TouchID: 'mock1', FaceID: 'mock2', .... } // static values
};
});
Jest docs clarify why this is necessary:
When using the factory parameter for an ES6 module with a default export, the __esModule: true property needs to be specified. This property is normally generated by Babel / TypeScript, but here it needs to be set manually. When importing a default export, it's an instruction to import the property named default from the export object
Beware! With this approach, object BiometryTypes will be mocked once and forever, while you probably want to test different variants of it. Maybe doMock and unmock could help to overcome this, but in my opinion much much more reliable is to create several .test.js files, each one would mock BiometryTypes in unique way(e.g. FaceID_and_TouchID.test.js, TouchID-only.test.js etc). jest.mock is quite magical because it's hoisted to the very top, using unmock/doMock may break if values have already caught by other modules or closures in the same module.

How can I create a new browser session and delete it on webdriverio?

I'm using an implementation of webdriverio with cucumberjs.
I would like that every scenario I run creates a new browser, and deletes it after the scenario finishes running.
I thought this could be achieved through the use of cucumber hooks
beforeScenario: async function (world) {
await browser.newSession(capabilities)
}
afterScenario: async function (world) {
await browser.deleteSession()
}
However, this doesn't work, using reloadSession() after the scenarios is not ideal because it reloads the browser after running individual scenarios, which is unnecessary.
I noticed that the test runner creates a new browser object every time is ran, it's there any way I can skip that and create it at the beforeScenario level?
You can handle this by creating an another class like 'DriverFactory', which takes browser name as constructor parameter. Based on this parameter, launch respective browser and return it.
In another class, create a static variable like public status driver. Assign above class returned driver object to this variable in your beforeScenario hook in case its value is undefined like:
beforeScenario: async function (world) {
if (driver === 'undefined') {
driver = DriverFactory.launchBrowser("browsername");
}
}
in this case, on every scenario this block will be executed and checks whether variable is undefined. If Yes, it creates/ launches a new browser instance, else it will not..
afterScenario: async function (world, result) {
const client = await browser.newSession(browser.capabilities);
const clientNew = Object.create(browser)
await clientNew.deleteSession()
browser.sessionId = client.sessionId
},
try this , but await browser.reloadSession() does the same thing , i am not sure what you mean by it doesn't work

What parameter should I feed to Frida `ObjC.api.class_addMethod()` to make it happy?

I want to use Frida to add a class method to the existing Objective C class on Mac OS. After I read the Frida docs, I tried the following code:
const NSString = ObjC.classes.NSString
function func (n) { console.log(n) }
var nativeCb = new NativeCallback(func, 'void', ['int'])
ObjC.api.class_addMethod(
NSString.handle,
ObjC.selector('onTest:'),
nativeCb,
ObjC.api.method_getTypeEncoding(nativeCb)
)
The above code looks straightforward. However, after the ObjC.api.class_addMethod() call, the attached App and the Frida REPL both froze, it looks that the pointers are not right.
I have tried many possible parameter values for a whole night but still can figure the problem out. What's wrong with my code?
Only two issues:
method_getTypeEncoding() can only be called on a Method, which the NativeCallback is not. You could pass it the handle of an existing Objective-C method that has the same signature as the one you're adding, or use Memory.allocUtf8String() to specify your own signature from scratch.
Objective-C methods, at the C ABI level, have two implicit arguments preceding the method's arguments. These are:
self: The class/instance the method is being invoked on.
_cmd: The selector.
Here's a complete example in TypeScript:
const { NSAutoreleasePool, NSString } = ObjC.classes;
const onTest = new NativeCallback(onTestImpl, "void", ["pointer", "pointer", "int"]);
function onTestImpl(selfHandle: NativePointer, cmd: NativePointer, n: number): void {
const self = new ObjC.Object(selfHandle);
console.log(`-[NSString onTestImpl]\n\tself="${self.toString()}"\n\tn=${n}`);
}
function register(): void {
ObjC.api.class_addMethod(
NSString,
ObjC.selector("onTest:"),
onTest,
Memory.allocUtf8String("v#:i"));
}
function test(): void {
const pool = NSAutoreleasePool.alloc().init();
try {
const s = NSString.stringWithUTF8String_(Memory.allocUtf8String("yo"));
s.onTest_(42);
} finally {
pool.release();
}
}
function exposeToRepl(): void {
const g = global as any;
g.register = register;
g.test = test;
}
exposeToRepl();
You can paste it into https://github.com/oleavr/frida-agent-example, and then with one terminal running npm run watch you can load it into a running app using the REPL: frida -n Telegram -l _agent.js. From the REPL you can then call register() to plug in the new method, and test() to take it for a spin.

How to override dojo's domReady

I want to override dijit._CssStateMixin's domReady() method.
Is there any way to override that instead of changing the listener mechanism in Dojo.
I tried overriding _cssMouseEvent() method in simple javascript, but it still does invoke dijit's _cssMouseEvent() from domReady().
I have tried following approach:
dojoConfig = {
map: {
'dijit/_CssStateMixin': {
'dojo/domReady': 'app/noop'
}
}
};
I have added 'app' folder and then 'noop.js' inside that.
noop.js has nothing in it:
define([], function () {
return function () {};
});
Even after this I can see that dijit.js's _CssStateMaxin domReady() getting called from listener.apply (code snippet pasted below)
var addStopImmediate = function(listener){
return function(event){
if(!event.immediatelyStopped){// check to make sure it hasn't been stopped immediately
event.stopImmediatePropagation = stopImmediatePropagation;
return listener.apply(this, arguments);
}
};
}
If your ultimate goal is to prevent the domReady callback in dijit/_CssStateMixin from running, your simplest bet is likely to re-map dojo/domReady to a different module that doesn't call the callback at all, when loaded via dijit/_CssStateMixin.
NOTE: Stripping out these handlers might have adverse visual effects on Dijit widgets which inherit _CssStateMixin, since it may hinder the application of Dijit CSS classes related to hover and focus. But if your concern is that _CssStateMixin is hampering performance, it may at least be worth a try to confirm or deny your suspicion.
First we have to create a simple module that returns a function that does nothing, which we will later substitute for dojo/domReady when loaded by dijit/_CssStateMixin, so that it can still call domReady but it won't execute the callback it passes.
For simplicity's sake I'll assume you already have a custom package that you can easily add a module to; for this example I'll assume it's called app. Let's create app/noop:
define([], function () {
return function () {};
});
Now let's configure the loader to map app/noop in place of dojo/domReady specifically when loaded by dijit/_CssStateMixin:
var dojoConfig = {
...,
map: {
'dijit/_CssStateMixin': {
'dojo/domReady': 'app/noop'
}
},
...
};
Now the offending domReady callback should no longer be run.
If you're curious about map, you can read more about it in this SitePen FAQ.

Grails integration test failing with MissingMethodException

I'm attempting to test a typical controller flow for user login. There are extended relations, as with most login systems, and the Grails documentation is completely useless. It doesn't have a single example that is actually real-world relevant for typical usage and is a feature complete example.
my test looks like this:
#TestFor(UserController)
class UserControllerTests extends GroovyTestCase {
void testLogin() {
params.login = [email: "test1#example.com", password: "123"]
controller.login()
assert "/user/main" == response.redirectUrl
}
}
The controller does:
def login() {
if (!params.login) {
return
}
println("Email " + params.login.email)
Person p = Person.findByEmail(params?.login?.email)
...
}
which fails with:
groovy.lang.MissingMethodException: No signature of method: immigration.Person.methodMissing() is applicable for argument types: () values: []
The correct data is shown in the println, but the method fails to be called.
The test suite cannot use mocks overall because there are database triggers that get called, the result of which will need to be tested.
The bizarre thing is that if I call the method directly in the test, it works, but calling it in the controller doesn't.
For an update: I regenerated the test directly from the grails command, and it added the #Test annotation:
#TestFor(UserController)
class UserControllerTests extends GroovyTestCase {
#Test
void testLogin() {
params.login = [email: "test1#example.com", password: "123"]
Person.findByEmail(params.login.email)
controller.login()
}
}
This works if I run it with
grail test-app -integration UserController
though the result isn't populated correctly - the response is empty, flash.message is null even though it should have a value, redirectedUrl is null, content body is empty, and so is view.
If I remove the #TestFor annotation, it doesn't work even in the slightest. It fails telling me that 'params' doesn't exist.
In another test, I have two methods. The first method runs, finds Person.findAllByEmail(), then the second method runs and can't find Person.findAllByEmail and crashes with a similar error - method missing.
In another weird update - it looks like the response object is sending back a redirect, but to the application baseUrl, not to the user controller at all.
Integration tests shouldn't use #TestFor. You need to create an instance of the controller in your test, and set params in that:
class UserControllerTests extends GroovyTestCase {
void testLogin() {
def controller = new UserController()
controller.params.login = [email:'test1#example.com', password:'123']
controller.login()
assert "/user/main" == controller.response.redirectedUrl
}
}
Details are in the user guide.
The TestFor annotation is used only in unit tests, since this mocks the structure. In the integration tests you have access of the full Grails environment, so there's no need for this mocks. So just remove the annotation and should work.
class UserControllerTests extends GroovyTestCase {
...
}