Redux Jest is not receiving value as expected - javascript

I'm getting
Expected value to equal:
[{"id": 1, "text": "Run the tests"}, {"id": 0, "text": "Use Redux"}]
Received:
[{"id": 0, "text": "Use Redux"}, {"id": 1, "text": "Run the tests"}]
I don't really understand on how to make this reducer test pass. I'm referencing various github projects to have a better understanding on testing. I'm not sure what i can do to make the test pass. Here is what i have.
Im testing using jest.
actions/actions.js
let nextTodoId = 0;
export const addPost = text => ({
type: 'ADD_POST',
id: nextTodoId++,
text
})
reducers/myPosts
const initialState = [
{
text: 'Use Redux',
id: 0
}
]
const myPosts = (state = initialState, action) => {
switch(action.type){
case 'ADD_POST':
const post = {
id:state.reduce((maxId, post) => Math.max(post.id, maxId), -1) + 1,
text:action.text,
}
return [...state, post];
default:
return state
}
}
export default myPosts
tests/reducers.js
import { addPost } from '../actions/actions';
import myPosts from '../reducers/myPosts';
import uuid from 'uuid';
describe('myPosts myPosts', () => {
it('should return the initial state', () => {
expect(myPosts(undefined, {})).toEqual([
{
text: 'Use Redux',
id: 0
}
])
})
it('should handle ADD_POST', () => {
expect(
myPosts([], {
type: 'ADD_POST',
text: 'Run the tests'
})
).toEqual([
{
text: 'Run the tests',
id: 0
}
])
expect(
myPosts(
[
{
text: 'Use Redux',
id: 0
}
],
{
type: 'ADD_POST',
text: 'Run the tests',
id:0
}
)
).toEqual([
{
text: 'Run the tests',
id: 1
},
{
text: 'Use Redux',
id: 0
}
])
})
})

The problem is that you're expanding the previous state prior to adding the new post...
change your reducer to this:
return [post, ...state];
The way you wrote it... the new post is placed at the end of the state array. If you want the new post to show up first this will fix the issue.

Related

Mocking a simple function in VueJS, JEST

I am struggling to mock the delete function in the lists component.
My test looks like this at the moment
describe("delete a todo", () => {
test("should have todo removed", async () => {
const deleteItem = jest.fn();
const items = [{ id: 1, name: "ana", isComplete: false }];
const wrapper = shallowMount(Todo, items);
console.log(wrapper);
const deleteButton = ".delete";
wrapper.find(deleteButton).trigger("click");
expect(deleteItem).toHaveBeenCalledWith("1");
});
currently, when I run the tests the error reads.
Test Error
The application works fine, but I am not mocking the delete function correctly in my test as a "New Note" is still being passed through. What am I doing wrong?
just in case it helps, here is a part of the file I am testing.
methods: {
addItem() {
if (this.newItem.trim() != "") {
this.items.unshift({
// id: createUID(10),
id: uuid.v4(),
completed: false,
name: this.newItem
});
this.newItem = "";
localStorage.setItem("list", JSON.stringify(this.items));
this.itemsLeft = this.itemsFiltered.length;
}
},
removeItem(item) {
const itemIndex = this.items.indexOf(item);
this.items.splice(itemIndex, 1);
localStorage.setItem("list", JSON.stringify(this.items));
this.itemsLeft = this.itemsFiltered.length;
},
Also for more code, you can get it from the following link :
https://github.com/oliseulean/ToDoApp-VueJS
I think you have to make some changes to your original test case
Change jest.fn() to jest.spyOn(Todo.methods, 'deleteItem') since you have to track calls to methods object in Todo component. Refer: https://jestjs.io/docs/jest-object
Wait for the click event to be triggered with await
Use toHaveBeenCalledTimes not toHaveBeenCalledWith("1")
So your final test case will look like this
describe("delete a todo", () => {
test("should have todo removed", async () => {
const removeItem = jest.spyOn(Todo.methods, 'removeItem')
const items = [{ id: 1, name: "ana", isComplete: false }];
const wrapper = shallowMount(Todo, items)
await wrapper.find('.delete').trigger('click')
expect(removeItem).toHaveBeenCalledTimes(1);
});
});

React useState hook and submit in multi step form

I am creating a multi step form using React JsonSchema Form. I want to update my state every time Next button is clicked on the page and finally Submit. React JsonSchema Form validates the entries only if the button is of type submit. So my Next button is submit button.
As the form will have multiple questions, my state is array of objects. Because of the asynchronous nature of useState, my updated state values are not readily available to save to the backend. How should I get final values?
When I debug I can see the data till previous step. Is there a way to make useState to behave like synchronous call?
Here is the full code:
const data = [
{
page: {
id: 1,
title: "First Page",
schema: {
title: "Todo 1",
type: "object",
required: ["title1"],
properties: {
title1: { type: "string", title: "Title", default: "A new task" },
done1: { type: "boolean", title: "Done?", default: false },
},
},
},
},
{
page: {
id: 1,
title: "Second Page",
schema: {
title: "Todo 2",
type: "object",
required: ["title2"],
properties: {
title2: { type: "string", title: "Title", default: "A new task" },
done2: { type: "boolean", title: "Done?", default: false },
},
},
},
},
];
interface IData {
id: Number;
data: any
};
export const Questions: React.FunctionComponent = (props: any) => {
const [currentPage, setCurrentPage] = useState(0);
const [surveyData, setSurveyData] = useState<IData[]>([]);
const handleNext = (e: any) => {
setSurveyData( previous => [
...previous,
{
id: currentPage,
data: e.formData,
},
]);
if (currentPage < data.length) setCurrentPage(currentPage + 1);
else //Here I want to submit the data
};
const handlePrev = () => {
setCurrentPage(currentPage - 1);
};
return (
<Form
schema={data[currentPage].page.schema as JSONSchema7}
onSubmit={handleNext}
>
<Button variant="contained" onClick={handlePrev}>
Prev
</Button>
<Button type="submit" variant="contained">
Next
</Button>
</Form>
);
};
You can incorporate useEffect hook which will trigger on your desired state change.
Something like this:
useEffect(() => {
// reversed conditional logic
if (currentPage > data.length) {
submit(surveyData);
}
}, [currentPage])
const handleNext = (e: any) => {
setSurveyData( previous => [
...previous,
{
id: currentPage,
data: e.formData,
},
]);
if (currentPage < data.length) setCurrentPage(currentPage + 1);
// remove else
};
On your last submit, you'll have all the previous data in surveyData, but you'll have the latest answer in e.formData. What you'll need to do is combine those and send that to the server.
// in your onSubmit handler
else {
myApiClient.send({ answers: [...surveyData, e.formData] })
}
I would refactor the new state structure to use the actual value of the state and not the callback value, since this will allow you to access the whole structure after setting:
const handleNext = (e: any) => {
const newSurveyData = [
...surveyData,
{
id: currentPage,
data: e.formData
}
];
setSurveryData(newSurveyData);
if (currentPage < data.length) {
setCurrentPage(currentPage + 1);
} else {
// submit newSurveryData
};
};
A side note: you'll also have to account for the fact that going back a page means you have to splice the new survey data by index rather than just appending it on the end each time.

How to mock a user module differently in each test?

I have the following unit test for my Vue component:
import { shallowMount } from '#vue/test-utils';
import OrganizationChildren from './OrganizationChildren.vue';
describe('OrganizationChildren', () => {
beforeEach(() => {
jest.resetModules();
});
it('passes', () => {
jest.doMock('#/adonis-api', () => {
return {
organization: {
family(id) {
return {
descendants: [],
};
},
},
};
});
const wrapper = shallowMount(OrganizationChildren, {
propsData: {
org: {
id: 1,
},
},
});
});
});
And in the Vue component, it does import { organization } from '#/adonis-api';. I'm temporarily just console.logging the imported organization object, to make sure it's correct. But I can see that it's not using the mocked version that I've specified. What am I doing wrong? My goal is to mock the family method differently in each it() block to test what happens if descendants is empty, if it contains 5 items, 100 items, etc.
Solved! I had a couple issues, as it turns out:
Not properly mocking #/adonis-api. I should mention that it only mocks stuff at the top level, so I had to use a factory function in jest.mock (see below).
I needed an await flushPromises() to allow the template to re-render after its created() method evaluated my mock function and stored the result in this.children.
Full test:
import { shallowMount, config } from '#vue/test-utils';
import flushPromises from 'flush-promises';
import OrganizationChildren from './OrganizationChildren.vue';
import { organization } from '#/adonis-api';
jest.mock('#/adonis-api', () => ({
organization: {
family: jest.fn(),
},
}));
describe('OrganizationChildren', () => {
config.stubs = {
'el-tag': true,
};
it('shows nothing when there are no children', async () => {
organization.family.mockResolvedValueOnce({
descendants: [],
});
const wrapper = shallowMount(OrganizationChildren, {
propsData: {
org: {
id: 1,
},
},
});
await flushPromises();
const h4 = wrapper.find('h4');
expect(h4.exists()).toBe(false);
});
it('shows children when provided', async () => {
organization.family.mockResolvedValueOnce({
descendants: [{ name: 'One' }, { name: 'Two' }],
});
const wrapper = shallowMount(OrganizationChildren, {
propsData: {
org: {
id: 1,
},
},
});
await flushPromises();
const h4 = wrapper.find('h4');
const list = wrapper.findAll('el-tag-stub');
expect(h4.exists()).toBe(true);
expect(list.length).toBe(2);
expect(list.at(0).text()).toBe('One');
expect(list.at(1).text()).toBe('Two');
});
});

Zapier JS conditional statement

I'm noob at JS, trying to write an APP for zapier. I have a test auth function that I can't get to fail when bad info is sent in.
Here is the test function:
require('should');
const zapier = require('zapier-platform-core');
const App = require('../../index');
const appTester = zapier.createAppTester(App);
describe('Triggers - Get Groups', () => {
zapier.tools.env.inject();
it('should get an array', done => {
const bundle = {
authData: { api_key: process.env.API_KEY },
inputData: {}
};
appTester(App.triggers['getgroup'].operation.perform, bundle)
.then(results => {
results.includes('id');
done();
})
.catch(results);
});
});
If successfull, a sample return should look like this:
{"id":1815,"name":"New Contacts","count":2}
A failure looks like this:
{"RESPONSE":"FAIL","REASON":"Invalid API key"}
Here is the getgroup function:
// Trigger stub created by 'zapier convert'. This is just a stub - you will need to edit!
const { replaceVars } = require('../utils');
const getList = (z, bundle) => {
let url = 'https://path.to/apisite?action=getGroups&apiKey={{api_key}}';
url = replaceVars(url, bundle);
const responsePromise = z.request({ url });
return responsePromise.then(response => {
response.throwForStatus();
return z.JSON.parse(response.content);
});
};
module.exports = {
key: 'getgroup',
noun: 'Getgroup',
display: {
label: 'Get Groups',
description: 'Triggers when loaded to pull groups.',
hidden: true,
important: false
},
operation: {
inputFields: [
{
key: 'group',
label: 'Groupget',
type: 'string',
required: false
}
],
outputFields: [
{
key: 'count',
type: 'string'
},
{
key: 'id',
type: 'string',
label: 'groupid'
},
{
key: 'name',
type: 'string',
label: 'groupname'
}
],
perform: getList,
sample: { count: 243, id: 27806, name: 'New Contacts' }
}
};
When I test auth on Zapier's website, I'd like auth to fail, and return the "REASON"
How do I do this?

JS: Stub a method to do unit test via testdouble

I'm trying to 'stub' a method via testdoubleJS to do a unit test for this method (doing npm test). It is the first time I'm doing this, so it is still hard to understand for me.
For my attempt - shown below - I do get the error TypeError: mediaAddImagePoint.run is not a function
This is how my method I want to test looks like:
import { ValidatedMethod } from 'meteor/mdg:validated-method'
import { LoggedInMixin } from 'meteor/tunifight:loggedin-mixin'
import { Media } from '/imports/api/media/collection.js'
const mediaAddImagePoint = new ValidatedMethod({
name: 'media.point.add',
mixins: [LoggedInMixin],
checkLoggedInError: { error: 'notLogged' },
validate: null,
run ({ id, x, y }) {
Media.update(
{ _id: id },
{
$push: {
'meta.points': {
id: Random.id(),
x,
y
}
}
}
)
}
})
And this is how I'm trying to test this method via testdouble:
import { expect } from 'chai'
import td from 'testdouble'
describe('media.point.add', function () {
describe('mediaAddImagePoint', function () {
let Media = td.object(['update'])
let ValidatedMethod = td.function()
let LoggedInMixin = td.function()
let mediaAddImagePoint
beforeEach(function () {
td.replace('meteor/mdg:validated-method', { ValidatedMethod })
td.replace('meteor/tunifight:loggedin-mixin', { LoggedInMixin })
td.replace('/imports/api/media/collection.js', { Media })
mediaAddImagePoint = require('../../imports/api/media/methods/imagePoints.js').mediaAddImagePoint
})
afterEach(function () {
td.reset()
})
it('should add image point', function () {
const query = { id: 'sampleID', x: 12, y: 34 }
mediaAddImagePoint.run(query)
td.verify(Media.update(query))
})
})
})

Categories

Resources