I am pretty much new with Redux-saga and working on unit test. API call function unit test code returns false even if it looks identical. What am I missing?
[Actions]
export const actions = {
readUsers: {
read: (data) => util.createAction('READ_USER', data),
request: () => util.createAction('READ_REQUEST'),
success: (data: any) => util.createAction('READ_SUCCESS', data),
failure: (error: any) => util.createAction('READ_FAILURE', error)
}
};
[Generator function]
export function* fetchUsers(action) {
yield put(actions.readUsers.request());
try {
let data = {id: action.payload.id, option: action.payload.option};
let response = yield call(API().getUsers, data);
if (response) {
yield put(actions.readUsers.success(response.users));
} else {
yield put(actions.readUsers.failure(response.error));
}
} catch (error) {
yield put(actions.readUsers.failure(error));
}
};
[Unit test]
const id = 'user-id';
const option = {};
const result = {users: []};
const action = {payload : {id, option}};
describe('user-saga', () => {
describe('fetchUsers', () => {
const generator = fetchUsers(action);
it('should return actions.readUsers.request()', () => {
assert.deepEqual(generator.next().value, put(actions.readUsers.request()));
}); // PASS
it('should return the Api.getUsers call', () => {
assert.equal(generator.next(result).value, call(Api().getUsers, action.payload));
}); // FAIL
it('should return actions.readUsers.success()', () => {
assert.deepEqual(generator.next(result).value, put(actions.readUsers.success(result)));
}); // FAIL
it('should be finished', () => {
assert.deepEqual(generator.next().done, true);
}); // PASS
})
});
[Test output]
First test passed.
Second test failed with this error message
AssertionError: { '##redux-saga/IO': true,
CALL: { context: null, fn: [Function: getUsers], args: [ [Object] ] } } deepEqual { '##redux-saga/IO': true,
CALL: { context: null, fn: [Function: getUsers], args: [ [Object] ] } }
Third test failed with this error message
AssertionError: undefined deepEqual { '##redux-saga/IO': true,
PUT: { channel: null,
action: { type: 'READ_SUCCESS', users: undefined } } }
Related
When I tried with basic repository provided typeorm, I think test is completed.
But to do 'unit test' with custom repository of typeorm is not working.
I think mocking custom repository has problem.
What I have to do for mocking custom repostitory?
Next are test file and source file to test.
Thanks.
quests.service.spec.ts
import { Test } from '#nestjs/testing';
import { getRepositoryToken } from '#nestjs/typeorm';
import { QuestsService } from 'src/quests/quests.service';
import { QuestRepository } from 'src/quests/repositories/quest.repository';
import { Repository } from 'typeorm';
import { Complete } from 'src/quests/entities/complete.entity';
import { Player } from 'src/players/entities/player.entity';
const mockRepository = () => ({
create: jest.fn(),
save: jest.fn(),
findOne: jest.fn(),
});
const mockQuestsRepository = {
save: jest.fn(),
findOne: jest.fn(),
findAllWithCompletes: jest.fn(),
findOneWithCompletes: jest.fn(),
};
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;
type MockQuestRepository = Partial<Record<keyof QuestRepository, jest.Mock>>;
describe('QuestsService', () => {
let service: QuestsService;
let playersRepository: MockRepository<Player>;
let completeRepository: MockRepository<Complete>;
let questsRepository: MockQuestRepository;
beforeAll(async () => {
const module = await Test.createTestingModule({
providers: [
QuestsService,
{
provide: getRepositoryToken(Player),
useValue: mockRepository(),
},
{
provide: getRepositoryToken(Complete),
useValue: mockRepository(),
},
{
provide: QuestRepository,
useValue: mockQuestsRepository,
},
],
}).compile();
service = module.get<QuestsService>(QuestsService);
playersRepository = module.get(getRepositoryToken(Player));
completeRepository = module.get(getRepositoryToken(Complete));
questsRepository = module.get(QuestRepository);
});
describe('questComplete', () => {
it('should fail if quest does not exist', async () => {
questsRepository.findOne.mockResolvedValue(undefined);
const result = await service.questComplete(-1, 1);
expect(result).toEqual({
ok: false,
message: 'cant find requested Quest.',
});
});
it('should fail if player does not exist', async () => {
questsRepository.findOne.mockResolvedValue(true);
playersRepository.findOne.mockResolvedValue(undefined);
const result = await service.questComplete(1, 1);
expect(result).toEqual({
ok: false,
message: 'cant find player.',
});
});
});
});
quests.service.ts
#Injectable()
export class QuestsService {
constructor(
#InjectRepository(Complete)
private readonly completes: Repository<Complete>,
private readonly quests: QuestRepository
) {}
async questComplete(questId: number, playerId: number) {
try {
const quest = await this.quests.findOne({ id: questId });
if (!quest)
return { ok: false, message: 'cant find requested Quest.' };
const player = await Player.findOne({ where: { id: playerId } });
if (!player)
return { ok: false, message: 'cant find player.' };
const isCompleted = await this.completes.findOne({ quest, player });
if (isCompleted)
return { ok: false, message: 'quest is already completed.' };
await this.completes.save(this.completes.create({ quest, player }));
return { ok: true };
} catch (error) {
return { ok: false, message: 'quest cant be completed.' };
}
}
}
I was struggling with a test issue for my custom useLazyQuery hook. My first test is passing but the second one is failing. What am doing wrong for the second test?
Here is the useLazyFetchCoin.tsx
export const useLazyFetchCoin = () => {
const [coins, setCoins] = useState<ICoin[]>([]);
useEffect(() => {
const coins = localStorage.getItem('coinsInfo');
if (coins) {
setCoins(JSON.parse(coins));
}
}, []);
const [getData, { loading, error }] = useLazyQuery(GET_COIN_PRICE_QUERY, {
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
onCompleted: (data) => {
const hasSameCoin = coins.some((f) => f.id === data.markets[0]?.id);
if (data.markets.length && !hasSameCoin) {
const allCoins = [...coins, data.markets[0]];
setCoins(allCoins);
localStorage.setItem('coinsInfo', JSON.stringify(allCoins));
} else if (data.markets.length <= 0) {
alertNotification('Coin not found !', Notification.ERROR);
}
if (hasSameCoin) {
alertNotification('This coin already exists on your list !', Notification.WARNING);
}
}
});
return { coins, setCoins, getData, loading, error };
};
Here is the test file
describe('useLazyFetchCoin custom hook', () => {
const QueryMock = [
{
request: {
query: GET_COIN_PRICE_QUERY,
variables: { code: 'BNB' }
},
result: {
data: {
markets: [
{
id: 'binance_bnb_eur',
baseSymbol: 'BNB',
ticker: {
lastPrice: '414.90000000'
}
}
]
}
}
}
];
const QueryWrongCodeMock = [
{
request: {
query: GET_COIN_PRICE_QUERY,
variables: { code: 'asd' }
},
result: {
data: {
markets: []
}
}
}
];
function getHookWrapper(mocks: any, code: string) {
const wrapper = ({ children }: any) => (
<MockedProvider mocks={mocks} addTypename={false}>
{children}
</MockedProvider>
);
const { result, waitForNextUpdate } = renderHook(() => useLazyFetchCoin(), {
wrapper
});
expect(typeof result.current.coins).toBe('object');
expect(result.current.loading).toBeFalsy();
expect(result.current.error).toBeUndefined();
// call the lazy function
act(() => {
result.current.getData({
variables: { code }
});
});
return { result, waitForNextUpdate };
}
it('should return an array of coins', async () => {
// Working correctly
const { result, waitForNextUpdate } = getHookWrapper(QueryMock, 'BNB');
await waitForNextUpdate();
expect(result.current.loading).toBeFalsy();
expect(result.current.coins[0]).toEqual({
id: 'binance_bnb_eur',
baseSymbol: 'BNB',
ticker: {
lastPrice: '414.90000000'
}
});
});
it('should return an empty array when requesting a wrong code', async () => {
// Not working
const { result, waitForNextUpdate } = getHookWrapper(QueryWrongCodeMock, 'asd');
await waitForNextUpdate();
expect(result.current.loading).toBeFalsy();
expect(result.current.coins[0]).toEqual([]);
});
});
I got this error message for the second test.
Expected: []
Received: {"baseSymbol": "BNB", "id": "binance_bnb_eur", "ticker": {"lastPrice": "414.90000000"}}
I don't get it because I'm using different queries for each test.
Also, the second test should receive an empty array when you pass a wrong code such as 'asd'.
How can write a proper test for it?
I fixed the problem. When I was changing the order test, It worked correctly.
I added a clear mock function for it.
clear mock
I have this tests in my nextjs app:
jest.mock('axios');
describe('Contacts', () => {
afterEach(() => {
axios.get.mockClear();
});
it('first test', async () => {
axios.get.mockImplementation((url: string) => {
if (url.includes('?page=1')) {
return Promise.resolve(ContactsFirstPageResponse);
} else {
return Promise.resolve(ContactsSecondPageResponse);
}
});
// ...
});
it('second test', async () => {
axios.get.mockImplementation(() => Promise.resolve(ContactsEmptyResponse));
// ...
});
});
First test passed, but second received response from ContactsFirstPageResponse
In addition I added auto clearing mocks on jest config:
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig');
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
resetMocks: true,
restoreMocks: true,
};
module.exports = createJestConfig(customJestConfig);
Please for help.
I'm currently trying to test my thunk action (getUserFeatureNames) to see if it calls a success action(getUserFeatureNamesSuccess) using jest. getUserFeatureNames thunk action currently resides in loginActions.js file which is import homeQueries(which i'm trying to mock). So far I'm getting the following error when running my jest test..
TypeError: _homeQueries2.default.getFeatureNames is not a function
How do i mock homeQueries.getFeatureNames?
function createStore(state = {}, expectActions = {}){
const mockStore = configureMockStore([thunk]);
return mockStore(state, expectActions);
}
describe("home_async_tests", () => {
test("getUserFeatureNamesSuccess action is called if request was success", (done) => {
jest.mock('../../../graphQL/homeQueries', () => {
return jest.fn(() => {
{
getFeatureNames: () =>{
return new Promise((resolve, reject) => {
let array = [{iconFile: 'Personalization.png', description: 'Personalization'},{iconFile: 'Home.png', description: 'Home'}];
resolve(array);
});
};
}
});
});
jest.dontMock('../../../app/redux/actions/homeActions');
let homeActions = require('../../../app/redux/actions/homeActions');
const expectedAction = {type: types.userFeatureNamesSuccess, payLoad: {isError: false, data: '' }};
const store = createStore();
store.dispatch(homeActions.getUserFeatureNames({token:"fdis4554" })).then(() => {
const actions = store.getActions();
expect(actions[0].type).toEqual(expectedAction.type);
expect(actions[0].payLoad.isError).toEqual(expectedAction.payLoad.isError);
done();
});
});
I assume that the module just return an object and not a function that returns an object, so your mock should look like this:
jest.mock('../../../graphQL/homeQueries', () = > ({
getFeatureNames: () = > {
return new Promise((resolve, reject) = > {
let array = [{
iconFile: 'Personalization.png',
description: 'Personalization'
}, {
iconFile: 'Home.png',
description: 'Home'
}];
resolve(array);
});
};
}
});
I'm having a hard time trying to test an async function that that dispatches another async function:
export function validateVerifyCode(payload, pin) {
return dispatch => {
dispatch(requesting());
return fetch(`${BASE_URL}/register`).then(resp => {
return resp.json()
})
.then(json => {
dispatch(hasSubscriptionCheck(json));
}
})
.catch(error => dispatch(hasError(error)))
}
}
function hasSubscriptionCheck(payload) {
return dispatch => {
dispatch(requesting());
return fetch(`${BASE_URL}/subscriptions`)
.then(resp => resp.json())
.then(json => {
if (json.length == 0) {
dispatch(handleStripeSubmit(payload));
}
})
.catch(error => dispatch(hasError(error)));
}
}
The problem is in my unit test it doesn't actually call hasSubscriptionCheck:
it('validates the verify code', (done) => {
const stub = sinon.stub(Stripe.card, 'createToken');
stub.onCall(0).returns(new Promise((resolve) => {
resolve({});
}));
nock(BASE_URL)
.defaultReplyHeaders({
'Content-Type': 'application/json',
'Authentication': token
})
.post('/register')
.reply(200, {id: 'userid1234'})
.get('/^subscriptions')
.reply(200, []);
const store = mockStore({});
const payload = {
...
};
store.dispatch(actions.validateVerifyCode(payload, '123456'))
.then(() => {
console.log(store.getActions());
})
.then(done)
.catch(done);
})
What's weird though is that when i console.log store.getActions(), I see 2 actions:
[ { type: 'REQUESTING' }, { type: 'REQUESTING' } ]
How I tell the test framework to invoke the second fetch?