Testing native event emitters and natives modules in React Native - javascript

I have a React Native component which communicates with a custom iOS class, so I make use of NativeModules en NativeEventEmitter to send commands to and receive commands from the native code.
import {NativeModules, NativeEventEmitter} from 'react-native';
/* inside the constructor I do some setup: */
const { NetworkManager } = NativeModules;
const emitter = new NativeEventEmitter(NetworkManager);
/* here I subscribe to an event from the emitter */
public startDiscovery() {
const deviceFoundSubscription = this._emitter.addListener(
"DeviceDiscovered",
(device) => this.deviceFound(device)
);
this.NetworkManager.startDiscovery();
}
This code works just fine, but now I wanted to write some tests with Jest and this is where I am stuck. How would I go ahead to write a test for the event listener? I want to simulate a DeviceDiscovered event in a Jest test, and then assert that the listener is called.

To solve my problem I have mocked RCTDeviceEventEmitter by using an everyday JS EventEmitter:
const EventEmitter = require('EventEmitter');
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
/**
* Mock the NativeEventEmitter as a normal JS EventEmitter.
*/
export class NativeEventEmitter extends EventEmitter {
constructor() {
super(RCTDeviceEventEmitter.sharedSubscriber);
}
}
Than in my setupFile for jest I imported the mock to replace the react-native implementation.
import NativeEventEmitter from './__mocks__/nativeEventEmitter';
import { NativeModules } from 'react-native';
// Mock for my native module where I create a spy for the methods
const mockNativeModules = {
NetworkManager: {
startDiscovery: jest.fn(),
stopDiscovery: jest.fn(),
connectToHost: jest.fn(),
sendMessage: jest.fn()
}
};
// Mock every native module you use
Object.keys(mockNativeModules).forEach((module => {
jest.doMock(module, () => mockNativeModules[module], { virtual:true });
}));
jest.doMock('NativeModules', () => mockNativeModules);
jest.mock('NativeEventEmitter');
And than finally my actual test code:
import {
NativeModules,
NativeEventEmitter,
} from 'react-native';
import { DiscoveryService } from '/services/discoveryService';
import { device1, device2 } from './../fixtures/devices';
const nativeEventEmitter = new NativeEventEmitter();
describe('Discovery Service', () => {
beforeEach(() => {
discoveryService.deviceDiscovered = jest.fn();
discoveryService.startDiscovery();
});
test('Should call startDiscovery on Native Module NetworkManager', () => {
nativeEventEmitter.emit('DeviceDiscovered', device);
expect(NativeModules.NetworkManager.startDiscovery).toHaveBeenCalledTimes(1);
expect(discoveryService.serverFound).toHaveBeenCalledWith(device1);
});
test('Should handle multiple discoveries', () => {
nativeEventEmitter.emit('DeviceDiscovered', device1);
expect(discoveryService.serverFound).toHaveBeenCalledWith(device1);
nativeEventEmitter.emit('DeviceDiscovered', device2)
expect(discoveryService.deviceFound).toHaveBeenCalledWith(device2);
});
});

in your jest/setup.js
add your relative path:
jest.mock(
'../node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter',
);

jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter.js', () => {
const { EventEmitter } = require('events');
return EventEmitter;
});

Related

jest mock vuex useStore() with vue 3 composition api

I'm trying to unit test a component where you click a button which should then call store.dispatch('favoritesState/deleteFavorite').
This action then calls an api and does it's thing. I don't want to test the implementation of the vuex store, just that the vuex action is called when you click the button in the component.
The Component looks like this
<template>
<ion-item :id="favorite.key">
<ion-thumbnail class="clickable-item remove-favorite-item" #click="removeFavorite()" slot="end" id="favorite-star-thumbnail">
</ion-thumbnail>
</ion-item>
</template>
import {useStore} from "#/store";
export default defineComponent({
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", props.item.id);
}
return {
removeFavorite,
}
}
});
The jest test
import {store} from "#/store";
test(`${index}) Test remove favorite for : ${mockItemObj.kind}`, async () => {
const wrapper = mount(FavoriteItem, {
propsData: {
favorite: mockItemObj
},
global: {
plugins: [store]
}
});
const spyDispatch = jest.spyOn(store, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
});
I have tried different solutions with the same outcome. Whenever the "trigger('click')" is run it throws this error:
Cannot read properties of undefined (reading 'dispatch') TypeError:
Cannot read properties of undefined (reading 'dispatch')
The project is written in vue3 with typescript using composition API and vuex4
I found a solution to my problem.
This is the solution I ended up with.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}

Testing effects NgRx Angular 11

I am having problems while trying to test effects in my Angular 11 project. I use the karma runner. It would seem that when I dispatch an action in my tests, the effects does't seem to respond to it.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let actions$: any;
let effects: SimpleEffects;
let store: MockStore<Store>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
effects = TestBed.inject(SimpleEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
actions$ = of(SimpleActionTypes.UnavailablePrice);
// Subscribe to the effect
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
I have tried many ways to enter the subscribe part of my effect (using marbles hot cold ...), but it doesn't seem to work. I have a failure indicating :
"SimpleEffects should fire with a default price FAILED
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)"
The micro project is hosted here : https://github.com/Ushelajyan/simple-ngrx-testing-effects
Thank you in advance for your help.
You override actions$ within your test (it-block). Unfortunately the TestBed.configureTestingModule() gets executed inside a beforeEach block, which happens right before the it-block gets executed.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let store: MockStore<Store>;
const createEffects = (actions$) => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
return TestBed.inject(SimpleEffects);
};
it('should be created', () => {
const effects = createEffects(of(undefined));
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
const actions$ = of(SimpleActionTypes.UnavailablePrice);
// Create the effect with your given mock
const effects = createEffects(actions$)
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
This should do the trick.
I added a createEffect function that configures your TestBed properly with the given actions$ mock and returns a new instance of SimpleEffects.
This instance can then be used to subscribe to its defined effects.

How to stub a module function with Cypress?

I want to create a test with Cypress that has a React component that uses an auth library (#okta/okta-react) with a HOC (withOktaAuth).
My component looks like this:
// Welcome.js
import { withOktaAuth } from '#okta/okta-react'
const Welcome = ({authState}) => {
return <div>{authState.isAuthenticated ? 'stubbed' : 'not working'}</div>
}
export default withOktaAuth(Welcome)
I tried to make a test like so:
// test.js
import * as OktaReact from '#okta/okta-react'
const withOktaAuthStub = Component => {
Component.defaultProps = {
...Component.defaultProps,
authState: {
isAuthenticated: true,
isPending: false
},
authService: {
accessToken: '123'
}
}
return Component
}
describe('Test auth', () => {
before(() => {
cy.stub(OktaReact, 'withOktaAuth').callsFake(withOktaAuthStub)
})
it('Stubs auth', () => {
cy.visit('/welcome')
cy.contains('stubbed')
})
})
When I run the test, the component still does not use the stubbed function. Any help is very much appreciated!
It's been 2 years that this questions was submitted, but for those who still encounters that error, Cypress provides a guide about Okta e2e testing: https://docs.cypress.io/guides/end-to-end-testing/okta-authentication#Adapting-the-back-end

Jest simulate 'click' works quirky and test fails

I'm trying to test a simple component, that looks like this
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export class App extends PureComponent {
handleClick = (event) => {
const { loadGreetings } = this.props
loadGreetings()
}
render () {
const { hi } = this.props
return (
<section>
<h1 className={styles.earlyDawn}>{hi}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
)
}
}
App.propTypes = {
hi: PropTypes.string,
loadGreetings: PropTypes.func
}
export default App
Here is my test file
import React from 'react'
import {App} from './index'
import {shallow} from 'Enzyme'
describe('Testing App container...', () => {
let props
beforeEach(() => {
props = {
loadGreetings: jest.fn().mockName('loadGreetings'),
hi: 'Hi from test'
}
})
test('should handle click on the button', () => {
const wrapper = shallow(<App {...props}/>)
const buttonHi = wrapper.find('button')
const instance = wrapper.instance()
expect(buttonHi.length).toBe(1)
jest.spyOn(instance, 'handleClick')
buttonHi.simulate('click')
expect(props.loadGreetings).toHaveBeenCalled()
expect(instance.handleClick).toHaveBeenCalled()
})
})
So the problem is in the second toHaveBeenCalled assertion that fails all the time. However, first toHaveBeenCalled seems to be working, which bothers me, because props.loadGreetings is called inside instance.handleClick. Could you please help me to find what may be the problem?
Dependencies: "react": "16.9.0", "react-dom": "16.9.0", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "jest": "^24.8.0",
A simpler approach would be to pass in some initial props and test your component based upon those initial props -- you'll also manipulate those props to add more assertions.
Here's a working example (click on the Tests tab to run the tests):
components/App/index.js
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class App extends PureComponent {
handleClick = () => {
this.props.loadGreetings();
};
render() {
const { message } = this.props;
return (
<section className="app">
<h1>{message}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
);
}
}
App.propTypes = {
message: PropTypes.string,
loadGreetings: PropTypes.func
};
export default App;
components/App/App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./index";
// define the passed in function here for easier testing below
const loadGreetings = jest.fn();
// initial props to pass into 'App'
const initProps = {
message: "hi",
loadGreetings
};
describe("Testing App container...", () => {
let wrapper;
beforeEach(() => {
// this resets the wrapper with initial props defined above
wrapper = shallow(<App {...initProps} />);
});
afterEach(() => {
// this clears any calls to the mocked function
// and thereby resets it
loadGreetings.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find(".app").exists()).toBeTruthy();
expect(loadGreetings).toHaveBeenCalledTimes(0);
});
it("initially renders a 'hi' message and then a 'goodbye' message", () => {
expect(wrapper.find("h1").text()).toEqual("hi");
// manipulating the initial 'message' prop
wrapper.setProps({ message: "goodbye" });
expect(wrapper.find("h1").text()).toEqual("goodbye");
});
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
// since we passed in 'loadGreetings' as a jest function
// we expect it to be called when the 'Handshake' button is
// clicked
wrapper.find("button").simulate("click");
expect(loadGreetings).toHaveBeenCalledTimes(1);
});
});
Not recommended (see below), but you can spy on the class method -- you'll have to work with instances, forceUpdate the instance, then invoke the handleClick method either manually, wrapper.instance().handleClick(), or indirectly via some element's event handler: wrapper.find("button").simulate("click") or wrapper.find("button").props().onClick().
The reason I don't recommend this testing strategy is that you're testing a React implementation (testing whether or not the element's event handler invokes your callback function). Instead, you can avoid that by asserting against whether or not a prop function is called and/or a prop/state change happens. This is a more standard and direct approach to testing the component -- as that is what we care about; we care that the props and/or state changes based upon some user action. In other words, by making assertions against the 'loadGreetings' prop being called we're already testing that the onClick event handler works.
Working example:
App.test.js (same testing as above, with the exception of this test):
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
const spy = jest.spyOn(wrapper.instance(), "handleClick"); // spying on the method class
wrapper.instance().forceUpdate(); // required to ensure the spy is placed on the method
wrapper.find("button").simulate("click");
expect(spy).toHaveBeenCalledTimes(1);
const mockedFn = jest.fn(); // setting the method as a mocked fn()
wrapper.instance().handleClick = mockedFn;
wrapper.instance().forceUpdate(); // required to update the method instance with a mocked fn
wrapper.find("button").simulate("click");
expect(mockedFn).toHaveBeenCalledTimes(1);
});

Testing With Jest and Enzyme. Spy On a method of a react component

I have a simple react component.
import React, { Component } from "react";
class SampleText extends Component {
handleChange = e => {
console.log(" perform other task");
this.otherTask();
};
render() {
return <input type="text" onChange={this.handleChange} id="text1" />;
}
}
export default SampleText;
I want to test if i change something in the input field the handleChange method is called.
This is what i have tried:
import React from "react";
import SampleText from "./SampleText";
import Adapter from "enzyme-adapter-react-16";
import { shallow, configure } from "enzyme";
configure({ adapter: new Adapter() });
test("input change handle function", () => {
const wrapper = shallow(<SampleText />);
const instance = wrapper.instance();
jest.spyOn(instance, "handleChange");
//simulate instance's onChange event here
wrapper.find('input[id="text1"]').simulate("change", {
target: { value: "some random text" }
});
expect(instance.handleChange).toHaveBeenCalled();
});
The problem is when i simulate the change it is actually entering in the original handleChange method. The error that i get:
TypeError: this.otherTask is not a function
How can i achieve this simple test? Maybe i have to simulate the change of the instance's input field rather than the wrappers but don't have a clue of how to do it.
I solved it after adding some little changes to my test code :)
test("input change handle function", () => {
const wrapper = shallow(<SampleText />);
const instance = wrapper.instance();
//here is the change.
const spy = jest.spyOn(instance, "handleChange").mockImplementation(() => {});
wrapper.instance().forceUpdate();
//simulate instance's onChange event here
wrapper.find('input[id="text1"]').simulate("change", {
target: { value: "some random text" }
});
expect(spy).toHaveBeenCalled();
});
so i had to add an empty mockImplementation and use
wrapper.instance().forceUpdate();
to make it work.

Categories

Resources