I am writing a Jest test for a Vue component's method. Specifically, I want to test that a Vuex action (I've mapped to this action in the component with mapActions) was called with a certain argument.
I mock the argument that should be passed to the action-- my test is close, but the argument the action receives has a bunch of extra information, and I'm not sure why.
The method in my component that I'm testing is:
triggerSave (trip) {
if (this.canSave) {
const tripToSave = { tripInProcess: true, trip: { tripId: trip.tripId, }, };
this.updateMyTrips(tripToSave);
}
},
Where updateMyTrips is the Vuex action I've mapped to in my component.
In my jest tests, I mock the action that I'm testing in a beforeEach (along with the store)
describe('TripPlanner', () => {
let actions;
let store;
beforeEach(() => {
actions = {
updateMyTrips: jest.fn(),
},
store = new Vuex.Store({
modules: {
trips: {
namespaced: true,
actions,
},
},
});
});
The test itself that I have is
test('trigger save calls updateMyTrips action', () => {
const mockTrip = [
{
tripId: 1234,
tripDestination: 'Rio di Janeiro',
},
];
const wrapper = shallowMount(TripPlanner, { store, localVue, propsData: { trip: mockTrip, canSave: true, }, },);
wrapper.vm.updateMyTrips(mockTrip[0]);
const mockUpdate = { tripInProcess: true, trip: { tripId: mockTrip.tripId }, };
expect(actions.updateMyTrips).toHaveBeenCalledWith(mockUpdate);
});
When I run the test, and look at the Expected/Received values, it does receive my mockUpdate object, but also receives
"commit": [Function anonymous],
+ "dispatch": [Function anonymous],
+ "getters": Object {},
+ "rootGetters": Object {},
+ "rootState": Object {
+ "addOns": Object {},
+ "state": Object {},
So the test fails.
If I just test that expect(actions.updateMyTrips).toHaveBeenCalled the test passes...but when I test that it was called WITH that specific argument, it fails.
I don't understand why the store's dispatch, getters, state, etc. are also being received!
The first argument is pass automatic, the second argument is your data that you are passing to vuex.
You can pass like first argument this:
expect.any(Object),
And the second argument will be your data
const mockTrip = [
{
tripId: 1234,
tripDestination: 'Rio di Janeiro',
},
];
I dont understand for what it not say nothing the documentation about this issue.
Related
Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html
I got this test from another site. They are injecting a mock route. I think I need to mock router itself or pass a real one into the test so the page can run. There is a way to do this in vue 2, but I haven't found an example for vue 3.
import { mount } from "#vue/test-utils";
import Nav from "./Nav.vue";
test("it displays a menu item", () => {
const mockRoute = {
params: {
id: 1,
},
};
const mockRouter = {
push: jest.fn(),
};
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
});
expect(wrapper.find("#navLabel_0").text()).toEqual("Appointments");
});
The component I'm testing has tags.
The test fails with:
Failed to resolve component: router-link
You have to pass the router-link as a stub: stubs: ['router-link'] when you mount the component:
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
stubs: ['router-link'] });
I am working with xstate with Nextjs. Now I am stuck somewhere.
import { assign, createMachine, interpret } from "xstate";
export interface toggleAuth {
isAuthenticated: boolean;
user: {
name: string | undefined;
};
}
// console.log(getCachedData());
export const authMachine = createMachine<toggleAuth>({
id: "auth",
initial: "unauthenticated",
context: {
isAuthenticated: false,
user: {
name: undefined,
},
},
states: {
authenticated: {
on: {
toggle: {
target: "unauthenticated",
},
},
entry: assign({
user: (ctx) => (ctx.user = { name: "Pranta" }),
isAuthenticated: (ctx) => (ctx.isAuthenticated = true),
}),
},
unauthenticated: {
on: {
toggle: {
target: "authenticated",
},
},
entry: assign({
user: (ctx) => (ctx.user = { name: undefined }),
isAuthenticated: (ctx) => (ctx.isAuthenticated = false),
}),
},
},
});
const service = interpret(authMachine);
service.onTransition((state) => console.log(state));
So I was watching the docs. According to them, whenever I transition from unauthenticated to authenticated and authenticated to unauthenticated, it should console log it for me. But it doesn't. It does only one time. What's happening here. Also, is it okay to define my machine like this? Thanks in advance.
It's not logging because you're not changing state; no event is ever being sent.
Please re-read the documentation on assigning to context - you are mutating context instead of assigning new values; the assigners should always be pure.
If you want to see the state change, you need to send a toggle event in this case:
service.send('toggle');
Also, there is no need for isAuthenticated; this is redundant, since that state is represented by the finite state (state.value) of your machine.
I'm trying to write unit tests which will test if the search query is right In other words if the logic written in where statement is returning expected results.
async function search(some_data){
return Event.findOne({
where: {
id: 123435,
[Op.or]: [
days: {
[Op.overlap]: some_data.days,
},
[Op.or]: [
{
startTime: {
[Op.gt]: some_data.start1,
},
endTime: {
[Op.lt]:some_data.end1,
},
},
{
startTime: {
[Op.lt]: some_data.start2,
[Op.lt]: some_data.end2,
},
endTime: {
[Op.gt]: some_data.end2,
[Op.gt]: some_data.start2,
},
},
],
],
},
})};
I need to test the result for different inputs.
I don't want to convert this test into integration test and use the original db, so I used sequelize-mock lib, but this returns only the result that I've defined and does not run the real query.
To test that your method is called with the correct parameters, you will need to use dependency injetion and a library to "spy" on your findOne method. In the example below, I am using Sinon
// app.js
// Note that "Event" must be used an argument in order to mock it out
async function search(Event, some_data) {
return Event.findOne({
where: {
id: 123435
}
})
}
If your test file:
// your test file
const app = require('./app');
const sinon = require('sinon');
const EventMock = {
findOne: sinon.spy()
};
describe('search', () => {
it('should call with the right parameters', () => {
const some_data = {};
search(EventMock, some_data);
assert(EventMock.findOne.calledWith({
where: {
id: 123435
}
}))
});
});
I am modularizing my schema for a GraphQL API and trying to merge the resolvers without using any 3rd party libraries.
Is there a simple way to do this without Lodash.merge() or equivalent?
The Apollo Documentation says to use a library such as Lodash to merge() modularized resolvers. (http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing)
The problem seems to be that by their nature, the resolvers contain functions as properties, so they seem to be omitted when I access them via Object.assign() or even JSON.stringify().
If I console.log them, I see: {"Query":{},"Mutation":{}}
Here is what one of the resolvers looks like:
const productResolvers = {
Query: {
myProducts: (root, { userId }, context) => {
return [
{ id: 1, amount: 100, expiry: '12625383984343', created: '12625383984343' },
{ id: 2, amount: 200, expiry: '12561351347311', created: '12625383984343' },
{ id: 3, amount: 200, expiry: '11346347378333', created: '12625383984343' },
{ id: 4, amount: 350, expiry: '23456234523453', created: '12625383984343' },
];
},
},
Mutation: {
addProduct: (root, { userId }, context) => {
return { id: 350, amount: 100, expiry: '12625383984343', created: '12625383984343' };
},
}
};
Let's assume there is another one virtually identical called widgetResolvers.
Here is a fully functional block of code:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers
});
Here is what I'm trying to achieve:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers: Object.assign({}, productResolvers, widgetResolvers)
});
I haven't loaded in ability to use rest spread yet (https://babeljs.io/docs/plugins/transform-object-rest-spread/). I suspect it won't work for the same reason Object.assign() doesn't work.
Oh, and here is why I suspect this merge doesn't work: Why doesn't JSON.stringify display object properties that are functions?
If you're using Object.assign(), your Query and Mutation properties shouldn't end up empty, but you will run into an issue because, unlike lodash's merge(), it's not recursive. Object.assign() only compares the "direct" properties of the objects it's passed -- overriding properties of previous sources as it moves through the list.
Because Query and Mutation are properties of the objects being passed, each subsequent resolver override the previous object's Query and Mutation, with the resulting object only holding the Query and Mutation properties of the last object passed into Object.assign().
It's a lot less neat, but if you're bent on avoiding importing lodash, you could get the expected behavior this way:
const productResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const widgetResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const resolvers = {
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
}
Got type resolvers too? No problem:
const Widget = { ... ✂ ... }
const Product = { ... ✂ ... }
const resolvers = Object.assign(
{
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
},
Widget,
Product)