I am currently getting to grips with testing in Vuex. I have the following action:
import { fetchProfile } from '../api'
export const getProfile = ({ commit }) => {
return fetchProfile()
.then(async (profile) => {
await commit(types.SET_AUTHENTICATED, true)
await commit(types.SET_PROFILE, profile.user)
})
}
And then the following test:
jest.mock('../../src/api')
describe('task actions', () => {
it('fetchProfile commits user profile returned by api', async () => {
const profile = { first_name: 'John', last_name: 'Doe' }
fetchProfile.mockResolvedValue(profile)
const commit = jest.fn()
await actions.getProfile({ commit })
expect(commit).toHaveBeenCalledWith(types.SET_AUTHENTICATED, true)
expect(commit).toHaveBeenCalledWith('SET_PROFILE', profile)
})
})
This fails with
"SET_PROFILE"
as argument 1, but it was called with
"SET_AUTHENTICATED".
If I comment out the second expect, the test passes.
However, how do I test that both commits have happened correctly?
Any help or guidance would be much appreciated
Thanks.
I added await to the beginning of each expect and it works now!!
Related
I'm not able to mock chained function of sequelize.
In following example I can mock Query 1, but not Query 2
something.service.ts
// Query 1
await this.table2.findAll<table2>({
attributes: [
'field1'
],
where: {
id: someId
},
});
// Query 2
// returns []
let bill1: any = await this.table2.sequelize.query(`
SELECT
aa.field1,
bg.field2
FROM
table1 aa,
table2 bg
WHERE
bg.id = '${billId}'
AND
aa.id = bg.aggr_id;
`);
something.service.spec.ts
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
{
provide: getModelToken(table2),
useValue: {
// mock successful for query 1
findAll: jest.fn(() => [{}]),
// mock fails for query 2
sequelize: jest.fn().mockReturnValue([]),
query: jest.fn().mockReturnValue([]),
'sequelize.query': jest.fn().mockReturnValue([]),
},
}
],
}).compile();
With this code I'm receiving (for Query 2)
TypeError: this.table2.sequelize.query is not a function
I tried with following code, no luck
sequelize: jest.fn().mockReturnValue([]),
query: jest.fn().mockReturnValue([]),
'sequelize.query': jest.fn().mockReturnValue([]),
sequelize: jest.fn().mockReturnValue({
query: jest.fn(() => [])
})
You can utilize jest.fn().mockReturnThis() to mock the chained function in jest. I have tested this on mocking the TypeORM repository, something like this:
repository.mock.ts
export const mockedRepository = {
find: jest.fn(),
createQueryBuilder: jest.fn(() => ({ // createQueryBuilder contains several chaining methods
innerJoinAndSelect: jest.fn().mockReturnThis(),
getMany: jest.fn(),
})),
};
Somewhere in your service for example:
test.service.ts
//
async findAll(){
return await this.repository
.createQueryBuilder('tableName')
.innerJoinAndSelect('tableName.relation','relation' )
.getMany();
}
//
And finally the unit test spec:
test.service.spec.ts
const module = await Test.createTestingModule({
providers: [
TestService,
{
provide: getRepositoryToken(Test),
useValue: mockedRepository,
}
],
}).compile();
testService =
module.get<TestService>(TestService);
testRepository = module.get<Repository<Test>>(
getRepositoryToken(Test),
);
});
describe('when findAll is called', () => {
beforeEach(() => {
mockedRepository.createQueryBuilder.getMany.mockResolvedValue([]);
});
it('should call innerJoinAndSelect method once', async () => {
await testService.findAll();
expect(mockedRepository.createQueryBuilder.innerJoinAndSelect).toHaveBeenCalledTimes(1);
});
it('should return an empty array', async () => {
expect(await testService.findAll()).toBe([]);
});
});
This is not a real working example but I hope you get the idea.
Issue was with the problem statement itself, this.table.sequelize is an object NOT a function to be chained, following solution worked to mock it.
sequelize: { query: jest.fn(() => []) }
To mock chained functions Farista's solution works.
I’m having a hard time to mock/test this useLazyQuery case; the hook in the screen:
const [
getSpecificReport,
{ loading: contentLoading, error: contentError, data: content },
] = useLazyQuery<SpecificReportResponse>(
SPECIFIC_REPORT(testResultsData?.getTestResults.testType),
{
client: cmsClient, // <- this is a specific ApolloClient
fetchPolicy: "network-only",
onCompleted: () => {
setScreenData();
},
onError: (err) => {
// (...) omitted for simplification
},
}
);
The mock:
const mocks = [{
request: {
query: SPECIFIC_REPORT('Report Title'),
fetchPolicy: 'network-only',
},
result: {
data: {
allReports: [getReportTestData()],
} as SpecificReportResponse,
},
}]
The test:
...
const { getByText, getAllByText } = render(
<MockedProvider
mocks={mocks}
addTypename={false}
>
<ResultsScreen {...mockProps} />
</MockedProvider>
);
await waitFor(() => {
new Promise((resolve) => setTimeout(resolve, 3000));
expect(getByText(/Something/)).toBeTruthy();
(...)
}
...
What happens is that the screen (ResultsScreen) just acts as if not received the data, i.e. the first expectation fails.
I noticed that if I take off the specific client from the hook, the test works fine - but not the screen, which depends on that.
I wonder if I should pass a “mocked client” to the mocks[0].request or something - I already tried to do it, but no success so far.
Does anyone have any ideas?
Thanks in advance!
I solved it by mocking the client like this (didn't change anything else):
...
const mockCmsClient = new ApolloClient({
link: ApolloLink.from([ApolloLink.empty(), ApolloLink.empty(), ApolloLink.empty()]),
cache: new InMemoryCache(),
});
jest.mock('../../my-module-with-exported-client', () => ({
...jest.requireActual('../../my-module-with-exported-client'),
cmsClient: mockCmsClient,
}));
...
I am using jest and #testing-library/react-hooks to test hooks implemented with react-query in my React Native code.
The tests work ok, but at the end, I am getting:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
Here is my simplified code:
import { renderHook } from '#testing-library/react-hooks'
import React from 'react'
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const useSomething = () => {
return useQuery('myquery', () => 'OK')
}
beforeAll((done) => {
done()
})
afterAll((done) => {
done()
})
// test cases
describe('Testing something', () => {
it('should do something', async () => {
const queryClient = new QueryClient()
const wrapper = ({ children }: { children: React.ReactFragment }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
const { result, waitFor } = renderHook(() => useSomething(), { wrapper })
await waitFor(() => {
return result.current.isSuccess
})
expect(result.current.data).toBe('OK')
})
})
I tried using cleanup, done, unmount, etc. before each/all with no results. If I remove useQuery from useSomething, the problem disappears.
Any idea how to fix it?
This issue has been reported in the past here:
https://github.com/tannerlinsley/react-query/issues/1847
The issue is caused by the react-query garbage collection timer running, which defaults to 5 minutes. Solutions are, as described in the issue:
clearing the queryCache after each test:
afterEach(() => { queryClient.clear() });
setting cacheTime to 0 for your test, e.g. with: queryClient.setDefaultOptions({ queries: { cacheTime: 0 } })
using jest.useFakeTimers()
You could try defining a function like:
export function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
Then on your test before the expect:
await flushPromises();
More info here
I use react-native with graphql.
I have a query and tried to use refetch.
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: photoId,
},
});
when after I edit my comment, I want to refetch this query and put it on setState in order to change UI.
const onEditValid = async ({ comments }) => {
const commentId = await AsyncStorage.getItem("#commentId");
await editCommentMutation({
variables: {
id: parseInt(commentId),
payload: comments,
},
update: updateEditComment,
});
};
const updateEditComment = async (cache, result) => {
const {
data: {
editComment: { error, ok, id },
},
} = result;
if (ok) {
const commentId = await AsyncStorage.getItem("#commentId");
const { comments } = getValues();
await textRef.current.clear();
await refetch();
setState(updatePhoto);
await cache.modify({
id: `Comment:${commentId}`,
fields: {
payload(prev) {
return comments;
},
},
});
}
};
But UI doesn't change.
I tried to change UI by modifying cache and refetching data. But both fails for a week.. :(
I also raised the question about fail of cache modify
=> React Native: `cache.modity` doesn't work
But no one answers.
I really need your help.. please help me
I created a recordSaga function, its target is to record what actions have been dispatched during the saga.
export const recordSaga = async (saga, initialAction, state) => {
const dispatched = [];
const done = await runSaga(
{
dispatch: action => dispatched.push(action),
getState: () => state,
},
saga,
initialAction,
).done;
return {
dispatched,
done,
};
};
so let's say my saga is this one
export function* mySaga() {
const needToSave = yield select(needToSaveDocument);
if (needToSave) {
yield put(saveDocument());
yield take(SAVE_DOCUMENT_SUCCESS);
}
yield put(doSomethingElse())
}
I want to write two tests, which I expect to be the following
describe('mySaga', async () => {
it('test 1: no need to save', async () => {
const state = { needToSave: false }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
doSomethingElse()
])
})
it('test 2: need to save', async () => {
const state = { needToSave: true }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
saveDocument(),
doSomethingElse()
])
})
})
However, for the test 2 where there is a take in between, and of course jest (or its girlfriend jasmine) is yelling at me: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I know it is because runSaga is waiting for the take(SAVE_DOCUMENT_SUCCESS), but how can I mock that up ?
The answer stdChannel().put({type, payload})
Why ?
Using stdChannel you can dispatch after the first run.
How ?
import stdChannel;
add to the first param in runSaga;
call stdChannel().put(SAVE_DOCUMENT_SUCCESS);
Example
what worked for me
I left the first test as it is the expected final result, but the solution comes on the last 2.
import { runSaga, stdchannel } from 'redux-saga'
let dispatchedActions = [];
let channel;
let fakeStore;
beforeEach(() => {
channel = stdChannel(); // you have to declare the channel to have access to it later
fakeStore = {
channel, // add it to the store in runSaga
getState: () => "initial",
dispatch: (action) => dispatchedActions.push(action),
};
});
afterEach(() => {
global.fetch.mockClear();
});
it("executes getData correctly", async () => {
await runSaga(fakeStore, getData, getAsyncData("test")).toPromise();
expect(global.fetch.mock.calls.length).toEqual(1);
expect(dispatchedActions[0]).toEqual(setData(set_value));
});
it("triggers takeLatest and call getData(), but unfortunately doesn't resolve promise", async () => {
await runSaga(fakeStore, rootSaga)// .toPromise() cannot be used here, as will throw Timeout error
channel.put(getAsyncData("test")); // if remove this line, the next 2 expects() will fail
expect(global.fetch.mock.calls.length).toEqual(1);
// expect(dispatchedActions[1]).toEqual(setData(set_value)); // will fail here, but pass on the next it()
});
it("takes the promised data from test above", () => {
expect(dispatchedActions[1]).toEqual(setData(set_value));
});
this answer (about true code, not tests) helped me
By looking at recordSaga:
export const recordSaga = async (saga, initialAction, state) => {
It seems that you should pass {type: SAVE_DOCUMENT_SUCCESS} as a second argument (i.e initialAction). That should trigger the take effect.