Test Action of Redux-thunk use JEST - unit-testing

I want to write a test for Axios use Jest Framework. I'm using Redux.
Here is my function get-request of Axios
export const getRequest = a => dispatch => {
return axios
.get(a)
.then(function(response) {
dispatch({
type: FETCH_DATA,
payload: response.data
});
})
.catch(function(error) {
dispatch({ type: ERROR_DATA, payload: { status: error.response.status, statusText: error.response.statusText } });
});
};
thanks in advance :)

Here is the solution:
index.ts:
import axios from 'axios';
export const FETCH_DATA = 'FETCH_DATA';
export const ERROR_DATA = 'ERROR_DATA';
export const getRequest = a => dispatch => {
return axios
.get(a)
.then(response => {
dispatch({
type: FETCH_DATA,
payload: response.data
});
})
.catch(error => {
dispatch({ type: ERROR_DATA, payload: { status: error.response.status, statusText: error.response.statusText } });
});
};
index.spec.ts:
import axios from 'axios';
import { getRequest, FETCH_DATA, ERROR_DATA } from './';
describe('getRequest', () => {
const dispatch = jest.fn();
it('should get data and dispatch action correctly', async () => {
const axiosGetSpyOn = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: 'mocked data' });
await getRequest('jest')(dispatch);
expect(axiosGetSpyOn).toBeCalledWith('jest');
expect(dispatch).toBeCalledWith({ type: FETCH_DATA, payload: 'mocked data' });
axiosGetSpyOn.mockRestore();
});
it('should dispatch error', async () => {
const error = {
response: {
status: 400,
statusText: 'client error'
}
};
const axiosGetSpyOn = jest.spyOn(axios, 'get').mockRejectedValueOnce(error);
await getRequest('ts')(dispatch);
expect(axiosGetSpyOn).toBeCalledWith('ts');
expect(dispatch).toBeCalledWith({ type: ERROR_DATA, payload: error.response });
axiosGetSpyOn.mockRestore();
});
});
Unit test result and coverage:
PASS 45062447/index.spec.ts
getRequest
✓ should get data and dispatch action correctly (9ms)
✓ should dispatch error (2ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.551s, estimated 3s
Here is the code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/45062447

Related

Jest Mock nor working as expected throws an error

const axios = "axios";
jest.mock(axios);
axios.get.mockImplementation((url) => {
if(url === process.env.ENHANCED_CLAIM_STATUS_276_DETAILS){
return Promise.resolve({ status: 200, data: claim_response_276 });
}
if(url === process.env.ENHANCED_VALUE_ADDS_277_DETAILS){
return Promise.resolve({ status: 200, data: claim_response_277 });
}
});
i am trying to mock the api responses but it throws this error :
**TypeError: Cannot read properties of undefined (reading 'mockImplementation')**
The moduleName parameter should be a string. See API doc jest.mock(moduleName, factory, options)
An working example:
import axios from "axios";
jest.mock('axios');
describe('74929332', () => {
test('should pass', async () => {
axios.get.mockImplementation((url) => {
return Promise.resolve({ status: 200, data: 'claim_response_276' });
});
const result = await axios.get('http://localhost:3000/api')
expect(result).toEqual({ status: 200, data: 'claim_response_276' })
})
});
Test result:
PASS stackoverflow/74929332/index.test.js (14.419 s)
74929332
✓ should pass (3 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.559 s

How to mock typescript services and functions that use AWS resources?

I am having a Typescript backend structure and I want to create unit tests for all the functionalities. I am using JEST and aws-skd-mock for mocking AWS. I have tried some things but it seems I am not doing the right thing.
I have this service where I am getting a parameter from ParamterStore (amazon.service.ts):
import * as AWS from "aws-sdk";
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function(resolve, reject){
self.parameterStore.getParameter({
Name: param,
WithDecryption: true
}, function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({Error: 'ParameterNotFound'})
}
console.log('RES ', data.Parameter.Value);
return resolve(data.Parameter.Value)
})
})
}
}
Then, I mock whole amazon.service file, I mock SSM.getParameter with response in my test file (amazon.service.spect.ts):
import * as AWSMock from "aws-sdk-mock";
import * as AWS from "aws-sdk";
import {AmazonService} from "./amazon.service";
jest.mock('./amazon.service');
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
const ssmGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: 1546551668.495,
ARN: 'arn:aws:ssm:eu-test-1:123:NAME'
}
})
});
AWSMock.setSDKInstance(AWS);
AWSMock.mock('SSM', 'GetParameter', ssmGetParameterPromise);
amazonService = new AmazonService();
console.log(await amazonService.getParam('NAME'))
await expect(amazonService.getParam('NAME')).resolves.toBe('VALUE')
})
});
With this I get undefined when amazonService.getParam is called.
As I looked in the examples they are initializing new AWS.SSM() right after is mocked and call it from test, but I want to achieve that by calling my function. It seems like SSM is not mocked when my function is called.
Any suggestions how to do this right ?
You don't need to mock ./amazon.service.ts module. Here is the unit test solution without using aws-sdk-mock.
E.g.
amazon.service.ts:
import * as AWS from 'aws-sdk';
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function (resolve, reject) {
self.parameterStore.getParameter(
{
Name: param,
WithDecryption: true,
},
function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({ Error: 'ParameterNotFound' });
}
console.log('RES ', data.Parameter!.Value);
return resolve(data.Parameter!.Value);
},
);
});
}
}
amazon.service.spec.ts:
import * as AWS from 'aws-sdk';
import { AmazonService } from './amazon.service';
import { mocked } from 'ts-jest/utils';
import { AWSError } from 'aws-sdk';
import { GetParameterResult } from 'aws-sdk/clients/ssm';
jest.mock('aws-sdk', () => {
const mSSMInstance = {
getParameter: jest.fn(),
};
const mSSM = jest.fn(() => mSSMInstance);
return { SSM: mSSM };
});
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
amazonService = new AmazonService();
expect(AWS.SSM).toBeCalled();
const mSSMInstance = new AWS.SSM();
const mData = {
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: new Date(1995, 11, 17),
ARN: 'arn:aws:ssm:eu-test-1:123:NAME',
},
};
mocked(mSSMInstance.getParameter).mockImplementationOnce(
(params, callback?: (err: AWSError | null, data: GetParameterResult) => void): any => {
if (callback) {
callback(null, mData);
}
},
);
const actual = await amazonService.getParam('NAME');
expect(actual).toBe('VALUE');
});
});
unit test result with coverage report:
PASS stackoverflow/61871955/amazon.service.spec.ts (9.613s)
amazon service mock
✓ should get Parameter from Parameter Store (19ms)
console.log
IN getPARAM NAME
at AmazonService.<anonymous> (stackoverflow/61871955/amazon.service.ts:12:13)
console.log
RES VALUE
at stackoverflow/61871955/amazon.service.ts:24:19
-------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------|---------|----------|---------|---------|-------------------
All files | 86.67 | 50 | 100 | 85.71 |
amazon.service.ts | 86.67 | 50 | 100 | 85.71 | 21-22
-------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.925s

AWS Mock not able to mock Lambda invoke()

I am trying to mock an Lambda.invoke() call in my Jest. However, the mock call didn't work and instead executed the real invoke() method which leads to not authorized to perform: lambda:InvokeFunction. I couldn't find out what's wrong as I did the same for mocking DynamoDB.DocumentClient and it works without any issue.
Jest File:
describe('Mock Lambda', () => {
beforeAll(async () => {
AWSMock.mock('Lambda', 'invoke', (params, callback) => {
if (params.FunctionName === 'MyLambaInvocation') {
callback(null, { status: 200, data: { code: '0', message: 'Successful' }
}
});
const result = await (myTest.handler(event, context(), null) as Promise<APIGatewayProxyResult>);
});
Typescript File:
import { Lambda } from 'aws-sdk';
export function invokeLambda(eventObject) {
return new Promise<any>((resolve, reject) => {
const lambdaConfig = {
region: 'ap-southeast-1',
endpoint: process.env.IS_OFFLINE ? 'http://localhost:8080' : undefined
};
const lambda = new Lambda(lambdaConfig);
const params = {
FunctionName: 'MyLambaInvocation',
Payload: JSON.stringify(eventObject)
};
lambda.invoke(params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
Here is an example only using jestjs to mock.
index.ts:
import { Lambda } from 'aws-sdk';
export function invokeLambda(eventObject) {
return new Promise<any>((resolve, reject) => {
const lambdaConfig = {
region: 'ap-southeast-1',
endpoint: process.env.IS_OFFLINE ? 'http://localhost:8080' : undefined,
};
const lambda = new Lambda(lambdaConfig);
const params = {
FunctionName: 'MyLambaInvocation',
Payload: JSON.stringify(eventObject),
};
lambda.invoke(params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
index.test.ts:
import { invokeLambda } from './';
import { Lambda as LambdaMock } from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mLambda = { invoke: jest.fn() };
return { Lambda: jest.fn(() => mLambda) };
});
describe('Mock Lambda', () => {
it('should invoke lambda', async () => {
const mLambda = new LambdaMock();
const mResult = {};
(mLambda.invoke as jest.Mocked<any>).mockImplementationOnce((params, callback) => {
callback(null, mResult);
});
const actual = await invokeLambda({});
expect(actual).toEqual({});
expect(LambdaMock).toBeCalledWith({ region: 'ap-southeast-1', endpoint: undefined });
expect(mLambda.invoke).toBeCalledWith(
{
FunctionName: 'MyLambaInvocation',
Payload: JSON.stringify({}),
},
expect.any(Function),
);
});
it('should handle error if invoke failure', async () => {
const mLambda = new LambdaMock();
const mError = new Error('network');
(mLambda.invoke as jest.Mocked<any>).mockImplementationOnce((params, callback) => {
callback(mError);
});
await expect(invokeLambda({})).rejects.toThrow('network');
expect(LambdaMock).toBeCalledWith({ region: 'ap-southeast-1', endpoint: undefined });
expect(mLambda.invoke).toBeCalledWith(
{
FunctionName: 'MyLambaInvocation',
Payload: JSON.stringify({}),
},
expect.any(Function),
);
});
});
unit test results with coverage report:
PASS stackoverflow/60753252/index.test.ts
Mock Lambda
✓ should invoke lambda (8ms)
✓ should handle error if invoke failure (3ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 75 | 100 | 100 |
index.ts | 100 | 75 | 100 | 100 | 7
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.054s, estimated 11s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60753252

Mocking ApolloClient's client.query method with Jest

Update January 22nd 2020
The solution from #slideshowp2 is correct, but I could not get it to work at all, due to this TypeError:
TypeError: Cannot read property 'query' of undefined
Well it turned out to be my jest configuration that had resetMocks: true set. After I removed it, the test did pass. (I don't know why though)
Original question:
I need to execute a graphql query in a helper function outside of a React component using Apollo Client and after a bit of trial and error I went for this approach which is working as it is supposed to:
setup.ts
export const setupApi = (): ApolloClient<any> => {
setupServiceApi(API_CONFIG)
return createServiceApolloClient({ uri: `${API_HOST}${API_PATH}` })
}
getAssetIdFromService.ts
import { setupApi } from '../api/setup'
const client = setupApi()
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
})
return { data, errors, loading }
}
Now I am trying to write test tests for the getAssetIdFromService function, but I have trouble figuring out how to get the client.query method to work in tests.
I have tried the approach below including many others that did not work.
For this particular setup, jest throws
TypeError: client.query is not a function
import { setupApi } from '../../api/setup'
import { getAssetIdFromService } from '../getAssetIdFromService'
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
describe('getAssetIdFromService', () => {
it('returns an assetId when passed an externalId and the asset exists in the service', async () => {
const { data, errors, loading } = await getAssetIdFromService('e1')
// Do assertions
})
}
I assume I am missing something in relation to this part:
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
...but I cannot see it.
You didn't mock correctly. Here is the correct way:
getAssetIdFromService.ts:
import { setupApi } from './setup';
import { gql } from 'apollo-server';
const client = setupApi();
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`;
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
});
};
setup.ts:
export const setupApi = (): any => {};
getAssetIdFromService.test.ts:
import { getAssetIdFromService, GET_ASSET_ID } from './getAssetIdFromService';
import { setupApi } from './setup';
jest.mock('./setup.ts', () => {
const mApolloClient = { query: jest.fn() };
return { setupApi: jest.fn(() => mApolloClient) };
});
describe('59829676', () => {
it('should query and return data', async () => {
const client = setupApi();
const mGraphQLResponse = { data: {}, loading: false, errors: [] };
client.query.mockResolvedValueOnce(mGraphQLResponse);
const { data, loading, errors } = await getAssetIdFromService('e1');
expect(client.query).toBeCalledWith({ query: GET_ASSET_ID, variables: { externalId: 'e1' } });
expect(data).toEqual({});
expect(loading).toBeFalsy();
expect(errors).toEqual([]);
});
});
Unit test results with 100% coverage:
PASS apollo-graphql-tutorial src/stackoverflow/59829676/getAssetIdFromService.test.ts (8.161s)
59829676
✓ should query and return data (7ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
getAssetIdFromService.ts | 100 | 100 | 100 | 100 | |
--------------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.479s
Source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/59829676
Use blow mock class:
class ApolloClient {
constructor(uri: string, fetch: any, request: any) {}
setupApi() {
return {
query: jest.fn(),
};
}
query() {
return jest.fn();
}
}
module.exports = ApolloClient;
and add below line to jest.cofig.ts
moduleNameMapper: {
'apollo-boost': '<rootDir>/.jest/appolo-client.ts',
},

Using expect.any() with supertest to check response body

I'm trying to use supertest to check res.body with Jest, but the following snippet will always fail
request(app)
.post('/auth/signup')
.send(validEmailSample)
.expect(200, {
success: true,
message: 'registration success',
token: expect.any(String),
user: expect.any(Object),
});
But when I rewrite the test to check the body in a callback as follows:
test('valid userData + valid email will result in registration sucess(200) with message object.', (done) => {
request(app)
.post('/auth/signup')
.send(validEmailSample)
.expect(200)
.end((err, res) => {
if (err) done(err);
expect(res.body.success).toEqual(true);
expect(res.body.message).toEqual('registration successful');
expect(res.body.token).toEqual(expect.any(String));
expect(res.body.user).toEqual(expect.any(Object));
expect.assertions(4);
done();
});
});
The test will pass.
I'm sure it has something to do with expect.any(). As Jest's documentation says that expect.any and expect.anything can only be used together with expect().toEqual, and expect().toHaveBeenCalledWith()
I'm wondering if there's any better way to do it, to use expect.any in supertest's expect api.
You can use expect.objectContaining(object).
matches any received object that recursively matches the expected properties. That is, the expected object is a subset of the received object. Therefore, it matches a received object which contains properties that are present in the expected object.
app.js:
const express = require("express");
const app = express();
app.post("/auth/signup", (req, res) => {
const data = {
success: true,
message: "registration success",
token: "123",
user: {},
};
res.json(data);
});
module.exports = app;
app.test.js:
const app = require('./app');
const request = require('supertest');
describe('47865190', () => {
it('should pass', (done) => {
expect.assertions(1);
request(app)
.post('/auth/signup')
.expect(200)
.end((err, res) => {
if (err) return done(err);
expect(res.body).toEqual(
expect.objectContaining({
success: true,
message: 'registration success',
token: expect.any(String),
user: expect.any(Object),
}),
);
done();
});
});
});
Integration test result with coverage report:
PASS src/stackoverflow/47865190/app.test.js (12.857s)
47865190
✓ should pass (48ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
app.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.319s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47865190