Reputation: 6528
I have a Typescript/React Application leveraging Amplify to run in AWS. My Application leverages Amplify API (GraphQL) and Amplify Storage for working with Amazon Services.
in Amplify V5 I was able to Set Jest Functions for the relevant API calls so I could safely run unit tests.
setupTests.ts
import {API, Storage} from "aws-amplify";
/** Establish API mocking before all tests. */
beforeAll(() => {
URL.revokeObjectURL = jest.fn();
//Window.prototype.scroll = jest.fn();
window.HTMLElement.prototype.scroll = jest.fn();
window.HTMLDivElement.prototype.scroll = jest.fn();
jest.mock('aws-amplify');
API.graphql = jest.fn();
Storage.get = jest.fn();
Storage.copy = jest.fn();
Storage.remove = jest.fn();
});
I am now upgrading from V5 to V6 of amplify. The API changed.
instead of import {API, Storage} from "aws-amplify";
and then running the functions on those objects, the functions are imported directly.
import {generateClient} from 'aws-amplify/api'
import {getUrl, downloadData, copy, remove, uploadData} from 'aws-amplify/storage';
I've tried a few different things to mock generateClient in setupTests.ts but nothing has worked.
jest.mock('aws-amplify');
jest.mock('aws-amplify/api');
jest.mock('aws-amplify/storage');
jest.mock('aws-amplify/api',
() => { return { generateClient: () => { graphql: jest.fn() } };
});
jest.mock('generateClient', () => { graphql: jest.fn(); });
jest.mock('aws-amplify/api', () => {
return { generateClient: () => { graphql: jest.fn() } };
});
import * as API from 'aws-amplify/api';
beforeAll(() => {
API.generateClient = jest.fn();
const generateClient = jest.mocked(API).generateClient;
const generateClient = API.generateClient;
});
jest.mocked(generateClient).mockImplementation(() => ({graphql: jest.fn()}));
jest.mock('aws-amplify/auth');
jest.mock('getUrl', () => jest.fn());
jest.mock('downloadData', () => jest.fn());
jest.mock('uploadData', () => jest.fn());
jest.mock('copy', () => jest.fn());
jest.mock('remove', () => jest.fn());
// @ts-ignore
generateClient.mockImplementation(() => ({graphql: jest.fn()}));
// @ts-ignore
getUrl.mockImplementation(() => {});
// @ts-ignore
downloadData.mockImplementation(() => {});
// @ts-ignore
uploadData.mockImplementation(() => {});
// @ts-ignore
copy.mockImplementation(() => {});
// @ts-ignore
remove.mockImplementation(() => {});
When testing with
expect(client.graphql).not.toHaveBeenCalled();
where client = generateClient();
and was either later on in the same beforeAll
method or a separate test file. I kept getting errors that client.graphQL
is an actual function and not a mock, in the case of the *.mockImplementation()
example the error was about that function not being available, because the code wasn't mocked yet.
TL/DR: In Amplify V6, with the direct method, and generator Exports, how do I setup my tests to just Jest Mock Functions in place of the real calls in my production code for unit testing?
Update:
if I put
jest.mock('aws-amplify/api', () => ({
generateClient: jest.fn(() => { graphql: jest.fn() }),
}));
//@ts-ignore
when(generateClient).calledWith().mockReturnValue({ graphql: jest.fn() });
before describe(
in my a test class, It successfully, mocks generateClient()
and client.graphql(
.
However, with V5, I was able to set those mocks in setupTests.ts and then they were available in any test files that needed them, without having to duplicate a lot of mocking code, thanks to beforeAll(
.
The above code doesn't work in beforeAll(
so How can I safely store and set the mocking code in one place, so I'm not duplicating a bunch of code to setup mocks everywhere?
Update 2 per request, minimal test code:
import {generateClient} from "aws-amplify/api";
jest.mock('aws-amplify/api');
const client = generateClient();
describe('App', () => {
test('mocks Amplify correctly', () =>
{
console.log(`generateClient: ${generateClient}`);
expect(jest.isMockFunction(generateClient)).toBeTruthy();
expect(jest.isMockFunction(client)).toBeFalsy();
console.log(`client: ${client}`);
expect(client).toHaveProperty('graphql');
expect(jest.isMockFunction(client.graphql)).toBeTruthy()
expect(client.graphql).not.toHaveBeenCalled();
});
});
That's what I've been using to verify I built expected Mock object correctly.
I have some helper files and other tests that use when()
to tell the mock objects to returned canned test data.
like so:
test('Documents still display when getDocuments returns an error.',
async () =>
{
//setup mocking, with forced error
when(client.graphql)
.calledWith(expect.objectContaining({query: queries.listDocumentDetails} ))
.mockRejectedValue(errorDocList);
const { store } = renderPage(DASHBOARD_PATH, <Dashboard />, state);
//check page renders
expect(screen.getByText(RecentDocumentsTitle)).toBeInTheDocument();
//check for error message
const msg = `Failed to GET DocumentList: ${errorDocList.errors[0].message}`;
const errorMsg = buildErrorAlert(msg);
await waitFor(() => {
expect(store.getState().alertMessage).toEqual(errorMsg);
});
const doc = errorDocList.data.listDocumentDetails.items[1]!;
expect(screen.getByText(doc.eng_title)).toBeInTheDocument();
expect(screen.getByText(doc.bc_title)).toBeInTheDocument();
expect(screen.getByText(doc.ak_title)).toBeInTheDocument();
};
Upvotes: 1
Views: 743
Reputation: 6528
I figured out my problem.
The solution was Manual Mocks.
The Documentation says to create a __mocks__
folder next to node_modules
when mocking code from a module. That didn't work for my project. The solution was to put the __mocks__
folder at the root of the src
directory for my project.
Upvotes: 1