How to test multiple commits in a Vuex action - vuex

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

Mock sequelizeORM chained function in jest js

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.

Testing useLazyQuery with specific ApolloClient

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,
}));
...

React Native: 'Jest did not exit one second after the test run has completed' with #testing-library/react-hooks and react-query

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

Why useQuery refetch() doesn't work sometimes?

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

how to receive a take with runSaga / redux-saga

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.