I'm using Jest to test my React Native app. I'm trying to mock a call to AsyncStorage and I'm using the mock-async-storage package. Following their instructions, I've set up their simple my test file looks like this:
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import "react-native";
import MockAsyncStorage from "mock-async-storage";
import { AsyncStorage as storage } from "react-native";
import Station from "../src/models/Station";
import * as actions from "../src/redux/actions/stationActions";
const mockStore = configureMockStore([thunk]);
/* ...other tests */
describe("async fetching actions", () => {
describe("using MockAsyncStorage", () => {
const mock = () => {
const mockImpl = new MockAsyncStorage();
jest.mock("AsyncStorage", () => mockImpl);
};
mock();
it("Mock Async Storage working", async () => {
await storage.setItem("myKey", "myValue");
const value = await storage.getItem("myKey");
expect(value).toBe("myValue");
});
});
});
Following the example on the package's repo, my tests folder has a __mocks__ folder with this AsyncStorage.js file:
import MockAsyncStorage from '../../../lib/mockAsyncStorage';
export default new MockAsyncStorage();
and these files in the tests folder:
// AsyncStorage.js
export default {}
// UseStorage.js
import AsyncStorage from './AsyncStorage';
export const save = (k, v) => AsyncStorage.setItem(k,v);
export const get = k => AsyncStorage.getItem(k);
When I run the test, I get the following error:
ReferenceError: __DEV__ is not defined
63 |
64 | it("Mock Async Storage working", async () => {
> 65 | await storage.setItem("myKey", "myValue");
| ^
66 | const value = await storage.getItem("myKey");
67 | expect(value).toBe("myValue");
68 | });
at Object.__DEV__ (node_modules/react-native/Libraries/Performance/Systrace.js:27:28)
at Object.require (node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:14:18)
at Object.require (node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js:13:22)
at Object.require (node_modules/react-native/Libraries/BatchedBridge/NativeModules.js:13:23)
at Object.require (node_modules/react-native/Libraries/Storage/AsyncStorage.js:15:23)
at Object.require [as AsyncStorage] (node_modules/react-native/Libraries/react-native/react-native-implementation.js:180:12)
at storage (tests/fetchStations.test.js:65:13)
at tryCatch (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:45:40)
at Generator.invoke [as _invoke] (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:271:22)
at Generator.prototype.(anonymous function) [as next] (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:97:21)
at tryCatch (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:45:40)
at invoke (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:135:20)
at node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:170:11
at callInvokeWithMethodAndArg (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:169:16)
at AsyncIterator.enqueue (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:192:13)
at AsyncIterator.prototype.(anonymous function) [as next] (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:97:21)
at Object.<anonymous>.exports.async (node_modules/#babel/runtime/node_modules/regenerator-runtime/runtime.js:216:14)
at Object._callee (tests/fetchStations.test.js:64:38)
I've tried many things suggested in other answers and nothing has helped:
Adding babel-preset-react-native to my dev dependencies
Adding "globals": { "__DEV__": true } to my package.json under `"jest"
Switching my jest preset from jest-expo to react-native
Adding /* global __DEV__ */ at the top of my test file
How do I make this work??
Small update: This may actually not have anything to do with mocks. I've removed all attempts to mock AsyncStorage, and tried just testing my method that uses AsyncStorage. All I've done is:
describe("fetchStations(useCache)", () => {
beforeEach(async () => {
await store.dispatch(actions.fetchStations({ useCache: true }));
});
xit("should return an object with the stations in a 'stations' key", () => {
expect(store.getActions()).toEqual(
expect.arrayContaining(expectedGetActions)
);
});
});
Where actions.fetchStations includes a call to AsyncStorage.getItem. And I get the same __DEV__ is not defined error.
If you configure jest to use the react-native preset, it will set up the react native javascript environment for you including the global __DEV__ variable.
Related
I am trying to mock the init method provided by sentry-expo and so far, this is what I have come up with:
setupFilesAfterEnv.ts
import '#testing-library/jest-native/extend-expect';
import * as Sentry from 'sentry-expo';
import sentryTestkitSuite from 'sentry-testkit';
const DUMMY_DSN = 'https://acacaeaccacacacabcaacdacdacadaca#sentry.io/000001';
const { sentryTransport } = sentryTestkitSuite();
// https://stackoverflow.com/questions/44649699/service-mocked-with-jest-causes-the-module-factory-of-jest-mock-is-not-allowe
// Cannot use the imported module as a value directly
const mockSentryTransport = sentryTransport as jest.Mocked<
typeof sentryTransport
>;
jest.mock('sentry-expo', () => ({
...jest.requireActual('sentry-expo'),
init: (options?: Sentry.SentryExpoNativeOptions) => ({
...options,
transport: mockSentryTransport,
}),
}));
beforeAll(() =>
Sentry.init({
dsn: DUMMY_DSN,
release: 'test',
tracesSampleRate: 1,
beforeSend(event) {
return {
...event,
extra: { os: 'mac-os' },
};
},
}),
);
beforeEach(() => {
sentryTestkitSuite().testkit.reset();
});
All the test cases which have used Sentry to capture exceptions successfully pass.
Now, I have created a file for adding standard crash-reporting utilities:
crash-reporting.ts
import * as Sentry from 'sentry-expo';
import { getEnvironmentConfig } from '#utils/environment/environment';
const routingInstrumentation =
new Sentry.Native.ReactNavigationInstrumentation();
export const initialiseCrashReporting = () => {
return Sentry.init({
dsn: getEnvironmentConfig()?.sentryDSN,
// Enable it only when you install the Expo development build on your device/simulator
// If you enable it while running the app in Expo Go, native dependencies will not work as expected such as Sentry
enableInExpoDevelopment: __DEV__,
debug: __DEV__, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production,
environment: getEnvironmentConfig()?.appEnv ?? 'development',
tracesSampleRate: __DEV__ ? 1 : 0.2,
integrations: [
new Sentry.Native.ReactNativeTracing({
tracingOrigins: ['localhost', /^\//],
routingInstrumentation,
}),
],
});
};
export const { wrap: sentryWrap } = Sentry.Native;
I am trying to test the above crash-reporting module like so:
crash-reporting.test.ts
import * as Sentry from 'sentry-expo';
import { initialiseCrashReporting } from './crash-reporting';
jest.mock('sentry-expo', () => {
const originalModule = jest.requireActual('sentry-expo');
return {
...originalModule,
init: jest.fn(),
};
});
describe('Crash Reporting Test Suite', () => {
it('should initialise sentry', () => {
const initSpy = jest.spyOn(Sentry, 'init');
initialiseCrashReporting();
expect(initSpy).toHaveBeenCalled();
});
});
Even though initialiseCrashReporting gets called, spyOn never catches the event where init gets called.
I realised that the globally mocked sentry-expo never gets overridden with the one in the crash-reporting.test.ts file.
I have 2 below-given questions related to this problem:
How can I override the globally mocked modules? Or how can I be assured that by calling initialiseCrashReporting, I am initialising sentry?
Can we override global beforeall for specific test cases?
Thanks in anticipation!
I'm using NestJS 7.6.0 and I'm experiencing a problem with E2E testing.
I have a very simple module that exposes a /health route in its controller. I'd like to test this route using the E2E testing feature of nestJs documented here:
https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
I'm currently using the quite same code:
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/health (GET)', async() => {
return await request(app.getHttpServer())
.get('/health')
.expect(200)
});
});
If I run this test with npm run test I have this error:
npm run test
> dv-ewok-api#0.0.1 test ....
> jest
[Nest] 54638 - 28/06/2021, 14:53:30 [ExceptionHandler] Health Check has failed! {"ready":{"status":"down","message":"connect ECONNREFUSED 127.0.0.1:3000"}}
FAIL src/health/health.controller.spec.ts
● AppController (e2e) › /health (GET)
expected 200 "OK", got 503 "Service Unavailable"
32 | return await request(app.getHttpServer())
33 | .get('/health')
> 34 | .expect(200)
| ^
35 |
36 | });
37 | });
It seems that the httpServer is not running.
To make it work I have to modify the code like this:
...
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listen(3000); // <===== ADD THIS LINE
});
...
I just added a app.listen(3000) and the test passed. I don't know if this is an oversight in the documentation or if I did it wrong.
Thank you for your help.
Edit for adding some more details
For the HealthCheck part, I'am using the NestJS Terminus integration described here.
So, my app.module looks like this :
import { Module } from '#nestjs/common';
import { TerminusModule } from '#nestjs/terminus';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthController } from './health/health.controller';
#Module({
imports: [TerminusModule],
controllers: [AppController, HealthController],
providers: [AppService],
})
export class AppModule {}
As you can see, I import the TerminusModule and I declare my 2 controllers: AppController and HealthController.
In AppController I have just a /ping route that says "hello world"
import { Controller, Get } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get('/ping')
getHello(): string {
return this.appService.getHello();
}
}
And in HealthController I have just the /health route that calls the /ping on localhost.
import { Controller, Get } from "#nestjs/common";
import { HealthCheck, HealthCheckService, HttpHealthIndicator } from "#nestjs/terminus";
#Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
) {}
#Get()
#HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('ping', 'http://localhost:3000/ping'),
]);
}
}
And if you run tests with npm run test, it shows the 503 error linked above. To make it work, I need to add await app.listen(3000); in the beforeAll() method right after the init() (cf. above).
I think my problem comes from "terminus" because if I start a new NestJS project which does not use Terminus, e2e testing works fine, without the need to add the .listen().
Do I need to do something special in the createTestingModule() with TerminusModule?
Thank you
I am writing unit tests for VueJS components and have consulted the "Applying Global Plugins and Mixins" section of Vue Test Utils Common Tips. I have a component that depends on the Vuex store so it makes sense that I would transpose the example under that section for my purposes.
Here is my code for that component's specific .spec.js file:
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import store from '#/store'
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(store)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
This is pretty faithful to the example provided in the link above. However, the error I receive when I run the test suite is as follows:
Should I be installing the Vue store differently?
To elaborate on my comment, I believe it should look like the following, where you pass in the store on the mount() call.
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import Vuex from 'vuex'
import store from '#/store' //you could also mock this out.
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(Vuex)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
store,
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
I believe that you have something like this.$store.getters[someBeautifulGetterName] in you component. To make your tests mount the component your need to initialise store and pass it into your testing component. Just keep in mind that this would be a brand new instance of Vuex. Here is the code
import { shallowMount } from '#vue/test-utils'
import Vue from 'vue'
import Vuex from 'vuex'
import Tags from '#/components/Tags'
Vue.use(Vuex)
Vue.prototype.$store = new Vuex.Store()
const factory = (propsData) => {
return shallowMount(Tags, {
propsData: {
...propsData
}
})
}
describe('Tags', () => {
it("render tags with passed data", () => {
const wrapper = factory({ loading: true })
// TODO:
})
})
I've got this configureStore.js which configures my enhanced redux store and persists it to the localStorage:
// #flow
import 'babel-polyfill'
import { addFormSubmitSagaTo } from 'redux-form-submit-saga/es/immutable'
import { applyMiddleware, createStore, compose } from 'redux'
import { autoRehydrate, persistStore } from 'redux-persist-immutable'
import { browserHistory } from 'react-router'
import { combineReducers } from 'redux-immutable'
import { fromJS } from 'immutable'
import { routerMiddleware } from 'react-router-redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import sagas from './sagas'
export default function configureStore () {
const initialState = fromJS({ initial: { dummy: true } })
const sagaMiddleware = createSagaMiddleware()
const middleware = [ routerMiddleware(browserHistory), sagaMiddleware ]
const enhancer = compose(
autoRehydrate(),
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__()
)
const store = createStore(
combineReducers(rootReducer),
initialState,
enhancer
)
// Persist store to the local storage
persistStore(store, { blacklist: [ 'form', 'routing' ] })
// Decorate with Redux Form Submit Saga
// and create hook for saga's
const rootSaga = addFormSubmitSagaTo(sagas)
sagaMiddleware.run(rootSaga)
return store
}
Now I'm trying to test this file using Jest:
import configureStore from './../configureStore'
import * as reduxPersistImmutable from 'redux-persist-immutable'
import * as redux from 'redux'
import createSagaMiddleware from 'redux-saga'
describe('configureStore', () => {
it('persists the store to the localStorage', () => {
reduxPersistImmutable.persistStore = jest.fn()
redux.createStore = jest.fn()
createSagaMiddleware.default = jest.fn(() => {
run: () => jest.fn()
})
configureStore()
})
})
Everything runs smooth in this test until configureStore reaches the sagaMiddleware.run(rootSaga) line. It throws the following error:
FAIL app/__tests__/configureStore.js
● Console
console.error ../node_modules/redux-saga/lib/internal/utils.js:206
uncaught at check Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware
● configureStore › persists the store to the localStorage
Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware
at check (../node_modules/redux-saga/lib/internal/utils.js:50:11)
at Function.sagaMiddleware.run (../node_modules/redux-saga/lib/internal/middleware.js:87:22)
at configureStore (app/configureStore.js:38:18)
at Object.<anonymous> (app/__tests__/configureStore.js:17:60)
at process._tickCallback (../internal/process/next_tick.js:103:7)
This error indicates that the mock function does not work as I intend: it doesn't seem to be overwritten and is called from within redux-saga. Mocking redux.createStore works just fine.
Apparently, using jest.mock('redux-saga', () => () => ({ run: jest.fn() })) is the right way to mock redux-saga's createSagaMiddleware function.
I've some logic runs at onEnter/onLeave. I've used some setInterval at onEnter and clear them at onLeave using clearInterval.
How can I unit test above case?
<Route
component={App}
onEnter={onEnterApp}
onLeave={onLeaveApp}
path="/app">
Below is my test case but it fails ,
import React from 'react';
import App from '../components/views/app.jsx';
import {shallow, mount, render} from 'enzyme';
import {expect, assert} from 'chai';
import sinon from 'sinon';
import {mountWithIntl} from '../test_utils/intl-enzyme-test-helper.js';
describe(‘App renders correctly', () => {
it('When mounting set intervals', () => {
const wrapper = mountWithIntl(<App/>);
expect(window.x).to.exist;
expect(window.y).to.exist;
});
it('When unmounting clear intervals', () => {
const wrapper = mountWithIntl(<App/>);
wrapper.unmount();
expect(window.x).to.not.exist;
expect(window.y).to.not.exist;
});
});
The onEnter and onLeave props aren't tied to the componentWillMount and componentWillUnmount methods of your <App> component, so just mounting and unmounting the <App> will not call those functions.
Assuming you trust that React Router works, you can just test that your onEnterApp and onLeaveApp functions work properly
describe('onEnterApp', () => {
it('sets x and y', () => {
onEnterApp();
expect(global.x).to.exist;
expect(globa.y).to.exist;
});
});
If you want to verify that they are run when the URL matches the /app path, then you will need to involve a <Router>.
import createMemoryHistory from 'react-router';
describe('App', () => {
it('runs onEnterApp when navigating to /app', (done) => {
const history = createMemoryHistory(['/app']);
const holder = document.createElement('div');
render((
<Router history={history}>
<Route
component={App}
onEnter={onEnterApp}
onLeave={onLeaveApp}
path="/app">
</Router>
), holder, () => {
expect(global.x).to.exist;
expect(globa.y).to.exist;
done();
});
});
});
Testing onLeaveApp would require you to navigate to a new location with your history instance and then test that the desired global state exists.
history.push({ pathname: '/foo' })