I am very new to unit testing, and am trying to go through my react-redux project to write some tests.
Why is this test not working, and how could I make it pass?
Here is the test. I want to test my fetch posts action creator. This is for a small blog application.:
import configureStore from 'redux-mock-store'; // ES6 modules
import { findSinglePost, sendEdit, changeRedirect, checkBoxChange } from '../client/redux/actions/postActions';
import thunk from 'redux-thunk';
import axios from 'axios';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
describe('asynchronous action creators', () => {
it('should fetch posts', () => {
let store = mockStore({})
//my async action creator. It uses mock data that's in the same folder.
const fetchPosts = () => function(dispatch) {
dispatch({type: 'FETCH_POSTS'});
return axios.get('./MOCK.json').then((response) => {
dispatch({type: 'FETCH_POSTS_FUFILLED', payload: response.data});
}).catch((err) => {
dispatch({type: 'FETCH_POSTS_REJECTED', payload: err});
});
};
//this doesn't equal FETCH_POSTS_FUFILLED, it ends up equaling just "FETCH_POSTS"
return store.dispatch(fetchPosts()).then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual({type: 'FETCH_POSTS_FUFILLED'});
})
})
});
Here is jest's feedback. I want it to equal 'FETCH_POSTS_'FUFILLED', but it's returning 'FETCH_POSTS'. :
FAIL _test_\actions.test.js
● asynchronous action creators › should fetch posts
expect(received).toEqual(expected)
Expected value to equal:
{"type": "FETCH_POSTS_FUFILLED"}
Received:
{"type": "FETCH_POSTS"}
Difference:
- Expected
+ Received
Object {
- "type": "FETCH_POSTS_FUFILLED",
+ "type": "FETCH_POSTS",
}
88 | return store.dispatch(fetchPosts()).then(() => {
89 | const actions = store.getActions();
> 90 | expect(actions[0]).toEqual({type: 'FETCH_POSTS_FUFILLED'});
91 | })
92 | })
93 | });
at _test_/actions.test.js:90:26
PASS client\views\LoginPage\LoginPage.test.jsx
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 5 passed, 6 total
Snapshots: 0 total
Time: 1.49s
Ran all test suites related to changed files.
Also, here is the project's github repo if you want to try to run it.
Also, if there's a standard way in the industry that's more well known on how to do this, I'd love the advice.
Edit:
When I change actions[0] to actions[ 1] I get this error:
Expected value to equal:
{"type": "FETCH_POSTS_FUFILLED"}
Received:
{"payload": {Symbol(impl): {"message": "The string did not match the expected pattern.", "name": "SyntaxError", Symbol(wrapper): [Circular]}}, "type": "FETCH_POSTS_REJECTED"}
Difference:
- Expected
+ Received
Object {
- "type": "FETCH_POSTS_FUFILLED",
+ "payload": DOMException {
+ Symbol(impl): DOMExceptionImpl {
+ "message": "The string did not match the expected pattern.",
+ "name": "SyntaxError",
+ Symbol(wrapper): [Circular],
+ },
+ },
+ "type": "FETCH_POSTS_REJECTED",
Here is the picture form of jest's feedback:
The mocked store you are using will store all dispatched calls that have been made to it. In your case, two dispatch calls should be made, the first being FETCH_POSTS and the second being either FETCH_POST_FULFILLED or FETCH_POST_REJECTED.
Hence when you retrieve the dispatched actions from the mocked store, the first entry (which you are using in your expect) will be the FETCH_POSTS. You should check the second value in the array, which would be either FETCH_POSTS_FULFILLED or FETCH_POSTS_REJECTED based on how the promise is resolved in the function you are testing.
Related
I've a SSR App Nextjs 12 installed on AWS Amplify that's too slow.
Logging the getServerSideProps() this is the result:
It takes 9 seconds to load page but the code inside getServerSideProps takes less than 0.5 second.
This is the server log:
START RequestId: 94ced4e1-ec32-4409-8039-fdcd9b5f5894 Version: 300
2022-09-13T09:25:32.236Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 1 [09:25:32.236] -
2022-09-13T09:25:32.253Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 2 [09:25:32.253] -
2022-09-13T09:25:32.255Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 3 [09:25:32.255] -
2022-09-13T09:25:32.255Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 4 [09:25:32.255] -
2022-09-13T09:25:32.431Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 5 [09:25:32.431] -
2022-09-13T09:25:32.496Z 94ced4e1-ec32-4409-8039-fdcd9b5f5894 INFO 6 [09:25:32.496] -
END RequestId: 94ced4e1-ec32-4409-8039-fdcd9b5f5894
REPORT RequestId: 94ced4e1-ec32-4409-8039-fdcd9b5f5894 Duration: 9695.59 ms Billed Duration: 9696 ms Memory Size: 512 MB Max Memory Used: 206 MB
That's the code:
export async function getServerSideProps(context) {
console.log("1 [" + new Date().toISOString().substring(11, 23) + "] -");
let req = context.req;
console.log("2 [" + new Date().toISOString().substring(11, 23) + "] -");
const { Auth } = withSSRContext({ req });
console.log("3 [" + new Date().toISOString().substring(11, 23) + "] -");
try {
console.log("4 [" + new Date().toISOString().substring(11, 23) + "] -");
const user = await Auth.currentAuthenticatedUser();
console.log("5 [" + new Date().toISOString().substring(11, 23) + "] -");
const dict = await serverSideTranslations(context.locale, ["common", "dashboard", "footer", "hedgefund", "info", "etf", "fs"]);
console.log("6 [" + new Date().toISOString().substring(11, 23) + "] -");
return {
props: {
exchange: context.params.exchange,
ticker: context.params.ticker,
username: user.username,
attributes: user.attributes,
...dict,
},
};
} catch (err) {
return {
redirect: {
permanent: false,
destination: "/auth/signin",
},
props: {},
};
}
}
This is not the answer but rather an alternative.
I tried using Amplify for my implementation because getServerSideProps on the Vercel hobby account gives a function timeout error. However, I think the Next.js deployment to Amplify is not optimized yet.
Instead of using getServerSideProps, I used getStaticPaths and getStaticProps whereby I always limited the number of paths to fetch from my API.
On client side
export const getStaticPaths: GetStaticPaths = async () => {
// This route to my API only gets paths(IDs)
const res = await getFetcher("/sentences-paths");
let paths = [];
if (res.success && res.resource) {
paths = res.resource.map((sentence: any) => ({
params: { sentenceSlug: sentence._id },
}));
}
return { paths, fallback: "blocking" };
};
On API
const getSentencePaths = async (req, res) => {
const limit = 50;
Sentence.find(query)
.select("_id")
.limit(limit)
.exec()
.then((resource) => res.json({ success: true, resource }))
.catch((error) => res.json({ success: false, error }));
};
This means even if I have 100 000 sentences, only 50 are rendered at build. The rest of the sentences are generated on demand because we have fallback: "blocking". See docs
Here is how my getStaticProps looks like
export const getStaticProps: GetStaticProps = async ({ params }) => {
const sentenceSlug = params?.sentenceSlug;
const response = await getFetcher(`/sentences/${sentenceSlug}`);
let sentence = null;
if (response.success && response.resource) sentence = response.resource;
if (!sentence) {
return {
notFound: true,
};
}
return {
props: { sentence },
revalidate: 60,
};
};
As you can see above, I used revalidate: 60 seconds see docs but since you wanted to use getServerSideProps, that's not the perfect solution.
The perfect solution is On-Demand Revalidation. With this, whenever you make a change to data that's used in a page, for example, change the sentence content, you can trigger a webhook to regenerate your page created by getStaticProps. So, your page will always be updated.
Go through this youtube tutorial to implement on-demand revalidation, really comprehensive https://www.youtube.com/watch?v=Wh3P-sS1w0I&t=8s&ab_channel=TuomoKankaanp%C3%A4%C3%A4.
Next.js on Vercel works way faster and more efficiently. Hope I helped.
The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)
I'm new to Postman. My Postman is failing to match test criteria. Could anyone please help! It is returning true even if there is no match
pm.test('Hourly metrics report generated to only 1 building', () => {
_.each(jsonData.attributes, (item) => {
pm.expect(item.building_ref_id).to.include('Mounting_View-EN-1-Internal_Alpha')
})
})
{
"id": "423317",
"type": "space",
"attributes": {
"name": "RM_05_030",
"space_ref_id": "RM_05_030",
"building_ref_id": "80_Fen",
"floor_ref_id": "5"
}
}
I assume that the code above is incomplete and that the response is actually and array of objects and that jsonData contains the parsed response body as a JavaScript array of objects.
The _.each() function takes an array as its first parameter, so you should not pass jsonData.attributes. This is the reason that your test passed; jsonData.attributes isn't an array, your callback will never get executed and no failing assertions means a succeeding test.
You can iterate over jsonData and assert that every item has a nested attribute item.attributes.building_ref_id containing your string:
jsonData = pm.response.json()
pm.test('Hourly metrics report generated to only 1 building', () => {
_.each(jsonData, (item) => {
pm.expect(item.attributes.building_ref_id).to.include('Mounting_View-EN-1-Internal_Alpha')
})
})
I am using AWS step function to invoke lambda function like this.
return stepfunctions.startExecution(params).promise().then((result) => {
console.log(result);
console.log(result.output);
return result;
})
And result is
{ executionArn: 'arn:aws:states:eu-west-2:695510026694:...........:7c197be6-9dca-4bef-966a-ae9ad327bf23',
startDate: 2018-07-09T07:35:14.930Z }
But i want the result as output of final lambda function
I am going through https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/StepFunctions.html#sendTaskSuccess-property
There are multible function there i am confused which one could be used to get back result of final lambda function.
Same question is there on stackoverflow Api gateway get output results from step function? i dont want to call any function periodically and keep checking status.Even if i use DescribeExecution function periodically i will only get the status of execution but not the result i wanted. Is there any way or any function which returns promise and is resolved once all the lambda has executed and give back the result
You can't get back a result from a step function execution in a synchronous way.
Instead of polling the result of the step function on completion send a result to an SNS topic or SQS queue for further processing in the final lambda function or model the whole process in the step function state machine.
After doing some study and looking at various tutorial i realized that this stackoverflow answer Api gateway get output results from step function? gives a easier approach to solve the problem and get final result from step function, yes i am not sure about another approach and how to implement any new answer is always appreciated
This is my code to implement the same approach this might help someone.
// in function first start step function execution using startExecution()
var params = {
stateMachineArn: 'some correct ARN',
input: JSON.stringify(body)
};
return stepfunctions.startExecution(params).promise().then((result) => {
var paramsStatus = {
executionArn: result.executionArn
};
var finalResponse = new Promise(function(resolve,reject){
var checkStatusOfStepFunction = setInterval(function(){
//on regular interval keep checking status of step function
stepfunctions.describeExecution(paramsStatus, function(err, data) {
console.log('called describeExecution:', data.status);
if (err){
clearInterval(checkStatusOfStepFunction);
reject(err);
}
else {
if(data.status !== 'RUNNING'){
// once we get status is not running means step function execution is now finished and we get result as data.output
clearInterval(checkStatusOfStepFunction);
resolve(data.output);
}
}
});
},200);
});
return finalResponse
})
To be able to get the result of step function (example: combined gateway & step function). You need to:
1. startExecution,
2. wait for your state machine to finish the execution (to be sure make wait equivalent to timeout of your state machine => wait = TimeoutSeconds of your state machine)
3. call describeExecution with the receive executionArn from startExecution.
Note that startExecution is an async function and it's not waiting for the result.
In my case, I'm using Lambda named init to execute the 3 discussed steps:
Code lambda Init:
const AWS = require('aws-sdk')
exports.handler = async (event) => {
const stepFunctions = new AWS.StepFunctions();
const reqBody = event.body || {};
const params = {
stateMachineArn: process.en.stateMachineArn,
input: JSON.stringify(reqBody)
}
return stepFunctions.startExecution(params).promise()
.then(async data => {
console.log('==> data: ', data)
await new Promise(r => setTimeout(r, 6000));
return stepFunctions.describeExecution({ executionArn: data.executionArn }).promise();
})
.then(result => {
return {
statusCode: 200,
message: JSON.stringify(result)
}
})
.catch(err => {
console.error('err: ', err)
return {
statusCode: 500,
message: JSON.stringify({ message: 'facing error' })
}
})
}
Code stateMachine
Make sure that in your statemachine your returning "ResultPath".
{
"Comment": "Annoucement validation",
"StartAt": "contact-validation",
"Version": "1.0",
"TimeoutSeconds": 5,
"States": {
"contact-validation": {
"Type": "Task",
"Resource": "arn:aws:xxxxxxx:function:scam-detection-dev-contact",
"ResultPath": "$.res",
"Next": "WaitSeconds"
},
"WaitSeconds": {
"Type": "Wait",
"Seconds": 1,
"Next": "Result"
},
"Result": {
"Type": "Pass",
"ResultPath": "$.res",
"End": true
}
}
}
I'm receiving an error when trying to do a basic reducer test such as those found on the redux docs: https://redux.js.org/recipes/writing-tests#reducers
TypeError: Cannot read property 'GROUP_LIST_REQUEST' of undefined
22 |
23 | switch (action.type) {
> 24 | case GROUP_LIST_REQUEST:
25 | return {
26 | ...state,
27 | fetching: true,
at group (app/reducers/group.js:24:10)
at node_modules/redux/lib/combineReducers.js:53:24
at Array.forEach (native)
at assertReducerShape (node_modules/redux/lib/combineReducers.js:51:25)
at combineReducers (node_modules/redux/lib/combineReducers.js:107:5)
at Object.<anonymous> (app/reducers/all.js:23:16)
at Object.<anonymous> (app/store.js:31:1)
at Object.<anonymous> (app/models/api.js:31:1)
at Object.<anonymous> (app/actions/group.js:1:1)
at Object.<anonymous> (app/reducers/group.js:1:1)
at Object.<anonymous> (__tests__/groups/reducer.js:6:1)
The stack trace seems to imply that the whole app is being run, when I just want to run my reducer method in isolation.
But even weirder is that it can't find my action type in the reducer switch that I'm not even using in the test.
import reducer from '../../app/reducers/group';
import * as actions from '../../app/actions/group';
describe('group reducer', () => {
test('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({
selected: null,
fetching: false,
fetched: false,
error: null,
items: [],
});
});
});
Reducer:
import {
GROUP_LIST_REQUEST,
GROUP_LIST_SUCCESS,
GROUP_LIST_FAILURE,
} from '../actions/group';
export const initialState = {
selected: null,
fetching: false,
fetched: false,
error: null,
items: [],
};
export default function group(state = initialState, action) {
console.log(action);
switch (action.type) {
case GROUP_LIST_REQUEST:
return {
...state,
fetching: true,
fetched: false,
};
case GROUP_LIST_SUCCESS:
return {
...state,
fetching: false,
fetched: true,
items: action.groupList === undefined ? [] : action.groupList,
};
case GROUP_LIST_FAILURE:
return {
...state,
fetching: false,
fetched: false,
};
default:
return state;
}
}
When I log the action I get { type: '##redux/INIT' } which is weird because for the test I am passing an empty object.
EDIT: I am using Jest to test, maybe it has to do with the configs?
I figured it out. The stack trace was really the major clue. When I no longer imported my actions and put them direct in the reducer file it fixed the problem. The actions import was the culprit:
import {
GROUP_LIST_REQUEST,
GROUP_LIST_SUCCESS,
GROUP_LIST_FAILURE,
} from '../actions/group';
At the top of that file imported the api, which imported the store, then imported the reducers, then combined all the reducers, which of course included my group reducer I already included.
So, I'm not sure of the exact cause, but my test was definitely including way more code then it should have, which probably points to an architectural failure somewhere in the app.
My solution for now is to follow more of a ducks approach where actions are included with the reducer and action creators all in one file.
Another approach could be separating the action constants out into their own file.