I'm trying to test a custom hook which just returns graphql data in gatsby.
Here is what I have so far but it's giving me an error.
hook useMyData
import { useStaticQuery, graphql } from 'gatsby';
export default () => {
const {
content: { data },
} = useStaticQuery(graphql`
query myQuery {
content {
data {
views: 10
}
}
}
`);
return data;
};
Jest test
import useMyData from './useMyData';
jest.mock('./useMyData', () => ({
__esModule: true,
default: () => ({
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}),
}));
test('data is returned', () => {
const data = useMyData();
// console.log('data = ', data);
});
The above does not run the useStaticQuery. Any know how I would test this.
You need to directly mock the useStaticQuery method on gatsby module instead of on ./useMyData. Something along the lines of:
jest.mock('gatsby', () => ({
__esModule: true,
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}));
Related
I tried to test extra reducers in this way. Here is an example of a reducer with extra reducers and a small test that does not work for me because the state does not change.
import { createSlice } from "#reduxjs/toolkit";
export const initialState: SchemeState = {
schemePlace: undefined,
};
const schemeSlice = createSlice({
name: "scheme",
initialState,
extraReducers: (builder) => {
builder.addMatcher(schemeApi.endpoints.getSchemePlace.matchFulfilled, (state, action) => {
state.schemePlace = action.payload;
});
},
});
TEST
it("handle extraReducers", () => {
const action = {
type: schemeApi.endpoints.getSchemePlace.matchFulfilled,
payload: {
id: 1,
name: "str",
},
};
expect(reducer(undefined, action)).toEqual({
schemePlace: {
id: 1,
name: "str",
}
});
});
My example doesn't work, state doesn't change. I don't know which type do I need for action?
in order to cover all statements/branch/lines, I need to write two or more fn.spec.ts to test fn.ts, how can I merge fn.spec.ts and fn2.spec.ts to be one file ?
// fn.ts
export const getEnv = () => {
if (location.href.indexOf('localhost') !== -1 || /\d+\.\d+\.\d+\.\d+/.test(location.href)) {
return 'Test'
}
return 'Product'
}
// fn.spec.ts
describe('fn getEnv',()=>{
Object.defineProperty(window, 'location', {
value: {
href: 'http://192.168.2.3:9001'
},
})
const { getEnv } = require('./fn')
test('getEnv',()=>{
expect(getEnv()).toBe('Test')
})
})
// fn2.spec.ts
describe('fn getEnv',()=>{
Object.defineProperty(window, 'location', {
value: {
href: 'https://xx.com'
},
})
const { getEnv } = require('./fn')
test('getEnv',()=>{
expect(getEnv()).toBe('Product')
})
})
// jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom', // browser environment
}
Just merge it into one file with a clear expectation message.
import { getEnv } from './fn';
describe('fn', () => {
describe('getEnv', () => {
test('should return "Test" when window location is an IP address', () => {
Object.defineProperty(window, 'location', {
value: {
href: 'http://192.168.2.3:9001'
},
});
const actual = getEnv();
expect(actual).toBe('Test');
});
test('should return "Product" when window location is a domain', () => {
Object.defineProperty(window, 'location', {
value: {
href: 'https://xx.com'
},
});
const actual = getEnv();
expect(actual).toBe('Product');
})
});
});
I have a file called constants with a series of string values that get imported into the file where init() is defined. I want to setup my test so that the string values are different between each run. Right now I can only seem to have them be the same for all test runs.
jest.mock('../src/constants', () => ({
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
}));
describe('git', () => {
describe('init', () => {
it('should execute three commands', async () => {
await init();
expect(execute).toBeCalledTimes(3);
});
it('should fail if the deployment folder begins with /', async () => {
// I want the values of constants to be different in here.
});
});
});
How can I set it up so action and build have different values in each test run? I've tried moving the jest.mock call into each it statement but that doesn't seem to work. Is there a better pattern for this?
You can use jest.spyOn(object, methodName, accessType?) to mock the return value for each unit test case.
E.g.
contants.ts:
export function get() {
return {
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
};
}
index.ts:
import { get } from './constants';
export async function init() {
return get();
}
index.spec.ts:
import { init } from './';
import * as constants from './constants';
describe('git', () => {
describe('init', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('t-1', async () => {
const mConstants = {
build: '/',
action: {
pusher: {
name: 'asd',
email: 'as#cat'
},
gitHubToken: 'ccsa'
}
};
const spy = jest.spyOn(constants, 'get').mockReturnValueOnce(mConstants);
const actualValue = await init();
expect(actualValue).toEqual(mConstants);
expect(spy).toBeCalledTimes(1);
});
it('t-2', async () => {
const mConstants = {
build: 'build',
action: {
pusher: {
name: 'zzz',
email: 'www#cat'
},
gitHubToken: 'xxx'
}
};
const spy = jest.spyOn(constants, 'get').mockReturnValueOnce(mConstants);
const actualValue = await init();
expect(actualValue).toEqual(mConstants);
expect(spy).toBeCalledTimes(1);
});
});
});
Unit test result:
PASS src/stackoverflow/58758771/index.spec.ts (7.685s)
git
init
✓ t-1 (5ms)
✓ t-2 (2ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.829s
Update, we can also change the constants object for each unit test case.
Before running the unit test, we should store the original constants. After each test case is finished, we should restore the original constants. I use lodash.cloneDeep method to make a deep clone for constants.
constants.ts:
export const constants = {
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
};
index.ts:
import { constants } from './constants';
export async function init() {
return constants;
}
index.spec.ts:
import { init } from './';
import { constants } from './constants';
import _ from 'lodash';
const originalConstants = _.cloneDeep(constants);
describe('git', () => {
afterEach(() => {
_.assignIn(constants, originalConstants);
});
describe('init', () => {
it('t-1', async () => {
Object.assign(constants, {
build: '/',
action: {
...constants.action,
pusher: { ...constants.action.pusher, name: 'aaa', email: 'aaa#cat' },
gitHubToken: 'bbb'
}
});
const actualValue = await init();
expect(actualValue).toEqual({
build: '/',
action: {
pusher: {
name: 'aaa',
email: 'aaa#cat'
},
gitHubToken: 'bbb'
}
});
});
it('should restore original contants', () => {
expect(constants).toEqual({
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
});
});
});
});
Unit test result:
PASS src/stackoverflow/58758771/v2/index.spec.ts (10.734s)
git
init
✓ t-1 (7ms)
✓ should restore original contants (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 12.509s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58758771
So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});
I am planning to use XState for managing states in the backend of my application. When an api is called, a function will be called on successful state change. The result of the function call has to be returned as response of the api.
// Returns a Promise, e.g.:
// {
// id: 42,
// name: 'David',
// friends: [2, 3, 5, 7, 9] // friend IDs
// }
function getUserInfo(context) {
return fetch('/api/users/#{context.userId}').then(response =>
response.json()
);
}
// Returns a Promise
function getUserFriends(context) {
const { friends } = context.user;
return Promise.all(
friends.map(friendId =>
fetch('/api/users/#{context.userId}/').then(response => response.json())
)
);
}
const friendsMachine = Machine({
id: 'friends',
context: { userId: 42, user: undefined, friends: undefined },
initial: 'gettingUser',
states: {
gettingUser: {
invoke: {
src: getUserInfo,
onDone: {
target: 'gettingFriends',
actions: assign({
user: (context, event) => event.data
})
}
}
},
gettingFriends: {
invoke: {
src: getUserFriends,
onDone: {
target: 'success',
actions: assign({
friends: (context, event) => event.data
})
}
}
},
success: {
type: 'final'
}
}
});
interpret(friendsMachine).start()
I want the output of this of getUserFriends sent as a response from my api. How to wait for the transition and all the invocations to be completed?
You can use onDone (read the docs on invoking promises 📖)
Here's an example Express app that waits sequentially for 2 promises to finish, and then sends that data:
function eventuallyGet(value) {
return new Promise(res => {
setTimeout(() => {
res(value);
}, 1000)
})
}
const getUserMachine = Machine({
initial: 'fetchingName',
context: {
user: undefined
},
states: {
fetchingName: {
invoke: {
src: () => eventuallyGet('David'),
onDone: {
target: 'fetchingDetails',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
name: e.data
})
})
}
}
},
fetchingDetails: {
invoke: {
src: () => eventuallyGet({ location: 'Florida' }),
onDone: {
target: 'success',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
...e.data
})
})
}
}
},
success: {
type: 'final',
data: {
user: ctx => ctx.user
}
}
}
});
app.get('/user', function(request, response) {
interpret(getUserMachine)
.onDone(e => {
response.json(e.data);
})
.start();
});
You can see the code here: https://glitch.com/~pleasant-relish