Javascript unit testing errors, stubbed function still being called - javascript

I am trying to write some unit tests and I am getting errors in the test and I am trying to understand why the errors happen.
The unit test is for the index.ts file that calls the features/index.ts file. I am stubbing the default export from features/index.ts with sinon. But when I run the tests I get the following error TypeError: Cannot read property 'resolve' of undefined pointing at the file features/feature1.ts
I have added the relavant extracts from the tests and typescript files below.
features/feature1.ts
import path from "path";
import fs from "fs";
import {Setup} from "../types";
const TEMPLATE_ROOT = path.resolve(__dirname,"../../templates");
const INDEX_TEMPLATE = fs.readFileSync(TEMPLATE_ROOT, "index.js"), "utf8");
export const setup: Setup = async ({config, options}) => {
// Internal code removed
}
features/index.ts
import {setup as feature1} from "./feature1.ts";
import {setup as feature2} from "./feature2.ts";
type FeatureTypes = "feature1" | "feature2"
type Features = {
[key in FeatureTypes]: Setup;
};
const features: Features = {
feature1: feature1,
feature2: feature2
}
export default features
index.ts
import features from "./features"
import { Config, Options } from "./types";
export async function init(config: Config, options: Options): Promise<void> {
const nextFeature = options.features ? options.features.shift() : undefined;
if (nextFeature) {
// Other irrelevant code
await Promise.resolve(features[nextFeature]({ config, options }));
return init(config, options);
}
}
index.spec.ts
import { expect } from "chai";
import * as sinon from "sinon";
import { init } from '.'
import * as features from "./features";
import { Config, Options } from "./types"
describe("init", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
let featuresStub: sinon.SinonStub;
beforeEach(() => {
featuresStub = sandbox.stub(features, "default").returns({
feature1: sandbox.stub().resolves(),
feature2: sandbox.stub().resolves(),
});
});
afterEach(() => {
sandbox.restore();
});
it("should call setup features", async () => {
const setup: Setup = {
features: [
"feature1",
"feature2",
],
};
await init({}, options);
expect(featuresStub).to.have.been.calledOnce;
});
// rest of tests
});
I have also tried the changing the stub setup to be:
import * as feature1 from ".features/feature1";
import * as feature2 from ".features/feature2";
// Other code
describe("init", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
let feature1Stub: sinon.SinonStub;
let feature2Stub: sinon.SinonStub;
beforeEach(() => {
feature1Stub = sandbox.stub(feature1, "setup");
feature2Stub = sandbox.stub(feature2, "setup");
feature1Stub.resolves()
feature2Stub.resolves()
});
// Rest of code and tests
});
I don't know why it would be trying to run code const TEMPLATE_ROOT = path.resolve(__dirname,"../../templates"); if I have stubbed the function that calls it.

Figured it out the imports were wrong
import path from "path";
import fs from "fs";
should be:
import * as path from "path";
import * as fs from "fs";

Related

How to mock fake url with axios for unit testing?

Context
The URL in this app is only accessible in production and it is not able to access via local. When doing unit tests, I need to mock the response of that url.
What I got
Follow this tutorial
Code I have
saga.js
import {all, call, put, takeEvery} from 'redux-saga/effects';
import axios from 'axios';
async function myfetch(endpoint) {
const out = await axios.get(endpoint);
return out.data;
}
function* getItems() {
//const endpoint = 'https://jsonplaceholder.typicode.com/todos/1';
const endpoint = 'http://sdfds';
const response = yield call(myfetch, endpoint);
const items = response;
//test
console.log('items', items);
yield put({type: 'ITEMS_GET_SUCCESS', items: items});
}
export function* getItemsSaga() {
yield takeEvery('ITEMS_GET', getItems);
}
export default function* rootSaga() {
yield all([getItemsSaga()]);
}
You can see that I made endpoint as const endpoint = 'http://sdfds';, which is not accessible.
saga.test.js
// Follow this tutorial: https://medium.com/#lucaspenzeymoog/mocking-api-requests-with-jest-452ca2a8c7d7
import SagaTester from 'redux-saga-tester';
import mockAxios from 'axios';
import reducer from '../reducer';
import {getItemsSaga} from '../saga';
const initialState = {
reducer: {
loading: true,
items: []
}
};
const options = {onError: console.error.bind(console)};
describe('Saga', () => {
beforeEach(() => {
mockAxios.get.mockImplementationOnce(() => Promise.resolve({key: 'val'}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('Showcases the tester API', async () => {
const sagaTester = new SagaTester({
initialState,
reducers: {reducer: reducer},
middlewares: [],
options
});
sagaTester.start(getItemsSaga);
sagaTester.dispatch({type: 'ITEMS_GET'});
await sagaTester.waitFor('ITEMS_GET_SUCCESS');
expect(sagaTester.getState()).toEqual({key: 'val'});
});
});
axios.js
const axios = {
get: jest.fn(() => Promise.resolve({data: {}}))
};
export default axios;
I was hopping this will overwrite the default axios
Full code here
Summary
Need to overwrite default axios' return response.
In order to replace a module, you need to save your mock module in a specific folder structure: https://jestjs.io/docs/en/manual-mocks#mocking-node-modules
So it seems that in your project - https://github.com/kenpeter/test-saga/blob/master-fix/src/tests/saga.test.js - you need to move the __mocks__ folder the root, where package.json is. Then import axios normally like import mockAxios from 'axios', and jest should take care of replacing the module.

Mock javascript function imported from vue

I'm trying to set up tests for a vue component collector which is importing a Javascript module to collect some data.
components/collector.vue
<script>
import dataService from '#/services/dataService.js';
...
dataService.collectResults().then(results => {
this.data = results;
});
</script>
The javascript file:
services/dataService.js
let dataService = {
collectResults() {
[...]
return dataPromise;
}
[...]
}
export default dataService;
I'm using jest for the tests for the collector component, and trying to mock the collectResults method.
collector.spec.js
jest.mock('dataService');
import { mount } from '#vue/test-utils';
import collector from '#/components/collector.vue';
import VueRouter from 'vue-router';
describe('behaviour tests', () => {
let cmp;
beforeEach(() => {
Vue.use(VueRouter);
const router = new VueRouter();
cmp = mount(collector, {
router
});
it('dummy test', () => {
console.log(cmp.vm.data);
}
services/__mocks__/dataService.js
const dataServiceMock = jest.genMockFromModule('dataService');
function __collectResults() {
return new Promise(() => {
console.log('mocked!');
[...]
});
}
dataServiceMock.collectResults = __collectResults;
export default dataServiceMock;
However, when running the tests the original dataService.js file is being executed (which in my case leads to an exception).
What should be the correct approach to this case, considering that I've to mock ~10 functions while leaving others as they are?

Unit Test Redux Action - Thunk Undefined

Wondering if someone can point out what I expect is a stupid mistake.
I have an action for user login.
I'm trying to test this action, I've followed the redux documentation as well as the redux-mock-store documentation however I keep getting an error as follows:
TypeError: Cannot read property 'default' of undefined
4 | import thunkMiddleware from 'redux-thunk'
5 |
> 6 | const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
| ^
7 | const mockStore = configureStore(middlewares)
8 |
9 | describe("userActions", () => {
at Object.thunkMiddleware (actions/user.actions.spec.js:6:22)
My test code is as follows:
import {userActions} from "./user.actions";
import {userConstants} from "../constants/user.constants";
import configureStore from 'redux-mock-store'
import thunkMiddleware from 'redux-thunk'
const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares)
describe("userActions", () => {
describe("login", () => {
it(`should dispatch a ${userConstants.LOGIN_REQUEST}`, () =>{
const store = mockStore({});
return store.dispatch(userActions.login("someuser", "somepassword")).then(() => {
expect(store.getState().loggingIn).toBeTruthy();
});
})
})
});
I've double checked both redux-thunk and redux-mock-store are included in my npm dev dependencies as well as deleteing the node_modules directory and reinstalling them all with npm install.
Can anyone see what's going wrong?
Thanks
EDIT:
It seems i'm doing something fundamentally wrong, I've tried to simplify it almost back to a clean slate to find where the problem is introduced.
Even with this test:
import authentication from "./authentication.reducer";
import { userConstants } from "../constants/user.constants";
describe("authentication reducer", () => {
it("is a passing test", () => {
authentication();
expect("").toEqual("");
});
});
Against this:
function authentication(){
return "test";
}
export default authentication
I'm getting an undefined error:
● authentication reducer › is a passing test
TypeError: Cannot read property 'default' of undefined
6 |
7 | it("is a passing test", () => {
> 8 | authentication();
| ^
9 | expect("").toEqual("");
10 | });
at Object.<anonymous> (reducers/authentication.reducer.spec.js:8:9)
yes, according to that error, seems you have a problem with module dependencies. Take a look at your webpack configuration.
Concerning the redux-mock-store, I suggest you to create a helper for future testing needs:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
export default function(middlewares = [thunk], data = {}) {
const mockedStore = configureStore(middlewares)
return mockedStore(data)
}
and you will include it in your test cases and use like that:
beforeEach(() => {
store = getMockStore()
})
afterEach(() => {
store.clearActions()
})
If you wont test redux with thunk you can use redux-thunk-tester module for it.
Example:
import React from 'react';
import {createStore, applyMiddleware, combineReducers} from 'redux';
import {asyncThunkWithRequest, reducer} from './example';
import ReduxThunkTester from 'redux-thunk-tester';
import thunk from 'redux-thunk';
const createMockStore = () => {
const reduxThunkTester = new ReduxThunkTester();
const store = createStore(
combineReducers({exampleSimple: reducer}),
applyMiddleware(
reduxThunkTester.createReduxThunkHistoryMiddleware(),
thunk
),
);
return {reduxThunkTester, store};
};
describe('Simple example.', () => {
test('Success request.', async () => {
const {store, reduxThunkTester: {getActionHistoryAsync, getActionHistoryStringifyAsync}} = createMockStore();
store.dispatch(asyncThunkWithRequest());
const actionHistory = await getActionHistoryAsync(); // need to wait async thunk (all inner dispatch)
expect(actionHistory).toEqual([
{type: 'TOGGLE_LOADING', payload: true},
{type: 'SOME_BACKEND_REQUEST', payload: 'success response'},
{type: 'TOGGLE_LOADING', payload: false},
]);
expect(store.getState().exampleSimple).toEqual({
loading: false,
result: 'success response'
});
console.log(await getActionHistoryStringifyAsync({withColor: true}));
});
});

import and export module in same file

Does anyone know of a better way to do this?
The goal: import, use, and export createLogger from the same file (application entry point).
WebStorm gives me a duplicate declaration warning.
import createLogger from './logger';
const logger = createLogger('namespace');
export { default as createLogger };
export { * as plugins } from './plugins';
export setup = () => {
// ...
logger.log('');
}
export start = async () => {
// ...
logger.log('');
}
To export multiple functions from the same file just do this:
import createLogger from './logger';
const logger = createLogger('namespace');
import plugins from './plugins';
import anotherLib from './anotherLib';
const setup = () => {
// ...
logger.log('');
}
const start = async () => {
// ...
logger.log('');
}
// export everything without default
export { plugins,
createLogger,
anotherLib,
setup,
start}
You can import them in another file after this is done.
Here's a sandbox to see how it works.
Have a look at this documentation about the export statement.

How to mock third party React Native NativeModules?

A component is importing a library that includes a native module. Here is a contrived example:
import React from 'react';
import { View } from 'react-native';
import { Answers } from 'react-native-fabric';
export default function MyTouchComponent({ params }) {
return <View onPress={() => { Answers.logContentView() }} />
}
And here is the relevant part of Answers from react-native-fabric:
var { NativeModules, Platform } = require('react-native');
var SMXAnswers = NativeModules.SMXAnswers;
When importing this component in a mocha test, this fails on account that SMXAnswers is undefined:
How do you mock SMXAnswers or react-native-fabric so that it doesn't break and allows you to test your components?
p.s.: you can see the full setup and the component I'm trying to test on GitHub.
Use mockery to mock any native modules like so:
import mockery from 'mockery';
mockery.enable();
mockery.warnOnUnregistered(false);
mockery.registerMock('react-native-fabric', {
Crashlytics: {
crash: () => {},
},
});
Here is a complete setup example:
import 'core-js/fn/object/values';
import 'react-native-mock/mock';
import mockery from 'mockery';
import fs from 'fs';
import path from 'path';
import register from 'babel-core/register';
mockery.enable();
mockery.warnOnUnregistered(false);
mockery.registerMock('react-native-fabric', {
Crashlytics: {
crash: () => {},
},
});
const modulesToCompile = [
'react-native',
].map((moduleName) => new RegExp(`/node_modules/${moduleName}`));
const rcPath = path.join(__dirname, '..', '.babelrc');
const source = fs.readFileSync(rcPath).toString();
const config = JSON.parse(source);
config.ignore = function(filename) {
if (!(/\/node_modules\//).test(filename)) {
return false;
} else {
const matches = modulesToCompile.filter((regex) => regex.test(filename));
const shouldIgnore = matches.length === 0;
return shouldIgnore;
}
}
register(config);

Categories

Resources