How to test Flutter apps using flutter_redux? - testing

I tried to write the first smoke test for my Flutter app:
class MockStore extends Mock implements Store<AppState> {}
void main() {
MockStore mockStore;
setUp(() {
mockStore = MockStore();
when(mockStore.state).thenReturn(AppState.initial());
});
testWidgets('Login screen smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(App(mockStore,));
// Verify that title is shown.
expect(find.text('Sign in with google'), findsOneWidget);
});
}
But I'm getting:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building StoreConnector<AppState, _ViewModel>:
The method 'map' was called on null.
Receiver: null
Tried calling: map<_ViewModel>(Closure: (AppState) => _ViewModel)
How to properly mock the Store?

Your mock needs to return something for the onChange getter, eg:
when(mockStore.onChange).thenAnswer((_) =>
Stream.fromIterable([AppState.intial()]));

Related

fluent PageModel API with TestCafe

Am trying to provide test authors with a fluent PageModel api in TestCafe, like:
await MyApp // a Page Model class instance
.navigateTo(xyz) // clicks a button to navigate to a specific part in my app
.edit() // clicks the edit button
.setField(abc, 12.34)
.save()
.changeStatus('complete');
I had all the individual methods working as async methods that can be awaited individually, but that makes the code quite unreadable and as a result error prone.
However, whatever way I attempt to make the api fluent, it results in the following error:
Selector cannot implicitly resolve the test run in context of which it
should be executed. If you need to call Selector from the Node.js API
callback, pass the test controller manually via Selector's .with({ boundTestRun: t }) method first. Note that you cannot execute
Selector outside the test code.
The trick into making a fluent async api is imho switching from async functions to regular functions as methods and have those methods return a thenable 'this' value. And in order to prevent the await oscillating, the 'then' function needs to be removed once called (and then reinstalled when
A very basic example that reproduces the issue can be seen below:
import { Selector } from 'testcafe'
class MyPage {
queue: [];
async asyncTest() {
return await Selector(':focus').exists;
}
queuedTest() {
this.then = (resolve, reject) => {
delete this.then; // remove 'then' once thenable gets called to prevent endless loop
// calling hardcoded method, in a fluent api would processes whatever is on the queue and then resolve with something
resolve(this.asyncTest());
};
// In a real fluent api impl. there would be code here to put something into the queue
// to execute once the 'then' method gets called
// ...
return this;
}
}
fixture `Demo`
.page `https://google.com`;
test('demo', async () => {
const myPage = new MyPage();
console.log('BEFORE')
await myPage.asyncTest();
console.log('BETWEEN')
await myPage.queuedTest(); // Here it bombs out
console.log('AFTER')
});
Note that the sample above isn't showcasing a fluent api, it just demonstrates that calling methods that use Selectors through the 'then' function (which imho is key to creating a fluent api) results in the aforementioned error.
Note: I know what the error means and that the suggestion is to add .with({boundTestRun: t}) to the selector, but that would result in required boilerplate code and make things less maintainable.
Any thoughts appreciated
P.
In your example, a selector cannot be evaluated because it does not have access to the test controller (t). You can try to avoid directly evaluating selectors without assertion.
Here is my example of the chained Page Model (based on this article: Async Method Chaining in Node):
Page Model:
import { Selector, t } from 'testcafe';
export class MyPage {
constructor () {
this.queue = Promise.resolve();
this.developerName = Selector('#developer-name');
this.submitButton = Selector('#submit-button');
this.articleHeader = Selector('#article-header');
}
_chain (callback) {
this.queue = this.queue.then(callback);
return this;
}
then (callback) {
return callback(this.queue);
}
navigateTo (url) {
return this._chain(async () => await t.navigateTo(url));
}
typeName (name) {
return this._chain(async () => await t.typeText(this.developerName, name));
}
submit () {
return this._chain(async () => await t.click(this.submitButton));
}
checkName (name) {
return this._chain(async () => await t.expect(this.articleHeader.textContent).contains(name));
}
getHeader () {
this._chain(async () => console.log(await this.articleHeader.textContent));
return this;
}
}
Test:
import { MyPage } from "./page-model";
fixture`Page Model Tests`;
const page = new MyPage();
test('Test 1', async () => {
await page
.navigateTo('http://devexpress.github.io/testcafe/example/')
.typeName('John')
.submit()
.checkName('John')
.getHeader();
});

How to create a unit test with a mocked class that uses Testcafé's TestController?

I have a test that mocks a class using typemoq but i have an issue with one of its function.
In this function I call Testcafé's TestController to use a specific Role but it does not work as expected.
What can I do in order to have an instance of TestController that I can use outside a testcafé test?
Here is an example test:
import {IMock} from "typemoq/Api/IMock";
import {Mock} from "typemoq";
import {Role, t} from "testcafe";
import { expect } from "chai";
class TestClass {
public static async useRole(role: Role): Promise<boolean> {
await t.useRole(role);
return true;
}
}
describe("test mock using Testcafé's TestController", () => {
it('should switch role', async () => {
const role = Role.anonymous();
const mockedClass: IMock<typeof TestClass> = Mock.ofType<typeof TestClass>();
mockedClass.setup(x => x.useRole(role)).returns(async (role) => {
return TestClass.useRole(role);
});
const result = await mockedClass.object.useRole(role);
expect(result).to.eq(true);
});
});
When debugging you should get this:
Thanks in advance :)
You can use TestCafe functions only if a browser is opened and a TestCafe test is running, so they will not work in custom code.
The useRole method (and most of other methods) also works only when a browser is opened because the browser and server code interact inside this method. useRole is designed to get cookies and local storage from the client side. So, even if you were able to mock the useRole method, it would lose the most important part of its logic.

How to write Vue test cases for Vue method in a component using vue-test-utils and jest which has store dispatch function

i am writing jest test cases for my vue component
i have a method which calls store dispatch function
dismissed() {
this.$store.dispatch("alert/hideAlert");
},
in the test cases i am triggering the dismissed event like below and expecting the store action method to call
wrapper.vm.dismissed();
await wrapper.vm.$nextTick();
expect(actions.hideAlert).toHaveBeenCalled();
but the dismissed method call "wrapper.vm.dismissed();" throws below error while running test cases
Cannot read property 'dispatch' of undefined
How can i test this this vue method ?
I think this is the simplest way to do it.
const mockStore = { dispatch: jest.fn() }
const wrapper = shallowMount(YourComponent, {
mocks: {
$store: mockStore
}
}
wrapper.vm.dismissed();
await wrapper.vm.$nextTick();
expect(mockStore.dispatch).toHaveBeenCalledWith('alert/hideAlert');
But you can also do it in different ways.
Check this article

How to `setUp` a `WidgetTester` for Multiple Tests in Flutter

1. The Problem
The testWidgets function is apparently only a subcase of the test function.
A use case I'm trying to solve right now is to pump the same widget for multiple testWidgets, a setUp for multiple testWidgets. However, how can I do this if it creates a new instance inside each test?
I've tried to initialize a WidgetTester outside the tests, in the main(), but WidgetTester has only a private constructor:
class WidgetTester
extends WidgetController
implements HitTestDispatcher, TickerProvider {
WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
if (binding is LiveTestWidgetsFlutterBinding)
binding.deviceEventDispatcher = this;
}
I don't quite get how the Flutter team made this work, but initializing a WidgetTester in the same way they did inside the testWidgets function isn't working for me:
final TestWidgetsFlutterBinding binding
= TestWidgetsFlutterBinding.ensureInitialized()
as TestWidgetsFlutterBinding;
final WidgetTester tester = WidgetTester._(binding);
2. An Example
A simple example would be to try to break down the tests of the Flutter demo that is created with each new Flutter project from flutter create. In it, we could try to separate the initial setup test of the app from the tapping action test:
testWidgets('Initial setup', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
});
testWidgets('Increment the counter on tap', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
The idea would be to try to move the await tester.pumpWidget(MyApp()); into a setUp function.
Below is what looks like the current way to solve for this in Flutter.
To summarize:
Create the group(..) structure inside main()
Create your own private methods from inside that structure, for each group of testing you want. For each of these private methods:
Pass in the WidgetTester instance
Let them be async
And then you should only have a single call to testWidgets(..)
Inside this method, is where you call the private methods you set up to distribute test logic
Call each of these with await, so they don't run concurrently
So far I didn't find a way for the output to indicate each "sub-test" it ran, so just using print(...) statements for now.
This is a demo for some QR Code logic:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:qr_code_demo/app/appRoutes.dart';
import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
void main() {
group('MainPage navigation tests', () {
NavigatorObserver mockObserver;
_loadAppHomeScreen(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
routes: AppRoutes.getRouteMap(),
home: AppHomeScreen(),
navigatorObservers: [mockObserver],
),
);
}
setUp(() {
mockObserver = MockNavigatorObserver();
});
Future<Null> _verifyLayoutElements(WidgetTester tester) async {
print('_verifyLayoutElements');
expect(find.byIcon(Icons.scanner), findsOneWidget);
expect(find.byType(FloatingActionButton), findsOneWidget);
expect(find.byType(RaisedButton), findsOneWidget);
}
Future<Null> _navigateToQrScannerScreen(WidgetTester tester) async {
print('_navigateToQrScannerScreen');
await tester.tap(find.byIcon(Icons.scanner));
await tester.pumpAndSettle();
verify(mockObserver.didPush(any, any));
expect(find.byType(AppHomeScreen), findsNothing);
expect(find.byType(QrScannerScreen), findsOneWidget);
}
testWidgets('AppHomeScreen WidgetTester', (WidgetTester tester) async {
await _loadAppHomeScreen(tester);
await _verifyLayoutElements(tester);
await _navigateToQrScannerScreen(tester);
});
});
}
Thanks to:
https://iiro.dev/2018/08/22/writing-widget-tests-for-navigation-events/
Scroll to code for this file: test/navigation_test.dart
====
And double-thanks, because the navigation testing logic including with this example is thanks to #iiro's post: https://stackoverflow.com/a/51983194/2162226
Here is the appRoutes.dart file:
import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';
class AppRoutes {
static const String AppHome = 'AppHome';
static const String QrScanner = 'QrScanner';
static String initialRoute() {
return AppHome;
}
static getRouteMap() {
return {
AppRoutes.AppHome: (context) => AppHomeScreen(),
AppRoutes.QrScanner: (context) => QrScannerScreen()
};
}
}
The answer is setUpAll() function. Use it first in the main method, outside of group and it runs only once.

Testing a function called on an object with Jest in React Native

EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})