How do I unit test a init() function with Jasmine? - javascript

I'm trying to write a unit test for an init function and I'm getting an error where I am calling collectionReport.init() in the test....
TypeError: undefined is not an object
This is the code I am trying to test...
class CollectionsReport {
constructor({ editCollectionsId, hasCollections}) {
this.editCollectionsId = editCollectionsId;
this.hasCollections = hasCollections
}
init({ id, name }) {
this.id = id;
this.name = name;
// need to test this
if (this.hasCollections) {
this.collection = this.collections.find(c => c.staticId === 'CAR-COLLECTION');
}
}
And this is my test so far
describe('CollectionsReport', () => {
const collectionArgs = {
editCollectionsId: jasmine.createSpy(),
hasCollections: false,
};
const collections = [
{
id: 1,
name: 'foo',
staticId: 'CAR-COLLECTIONS',
},
{
id: 2,
name: 'bar',
staticId: 'TRUCK-COLLECTIONS',
},
];
let collectionReport;
beforeEach(() => {
collectionReport = new CollectionsReport(collectionArgs);
});
describe('.init()', () => {
it('should test hasCollections', () => {
collectionReport.init();
//test this.hasCollections here
});
});
});
I'm sure its a mess, so please comment on how to fix and improve it.

Not sure what is the purpose of the CollectionsReport class, but maybe this will lead you to the right direction:
class CollectionsReport {
constructor({ editCollectionsId, hasCollections}) {
this.editCollectionsId = editCollectionsId
this.hasCollections = hasCollections
}
init({ collections, staticId }) {
this.hasCollections = !!collections.find(c => c.staticId === staticId)
}
}
describe('CollectionsReport', () => {
const collectionArgs = {
editCollectionsId: jasmine.createSpy(), // Not really using it
hasCollections: false
}
const collections = [
{
id: 1,
name: 'foo',
staticId: 'CAR-COLLECTIONS'
}, {
id: 2,
name: 'bar',
staticId: 'TRUCK-COLLECTIONS'
}
]
describe('.init()', () => {
let collectionReport
beforeEach(() => {
collectionReport = new CollectionsReport(collectionArgs)
})
it('should test hasCollections', () => {
collectionReport.init({ collections, staticId: 'CAR-COLLECTIONS' })
expect(collectionReport.hasCollections).toBe(true)
})
it('should test hasCollections', () => {
collectionReport.init({ collections, staticId: 'SOMETHING-ELSE' })
expect(collectionReport.hasCollections).toBe(false)
})
})
})

Related

How to initialize data correctly in vue js?

It takes the initTableData and initTableFiltered after onChangeDistrict which i hope to not take it now, instead to take only the initTableFiltered
data () {
return {
data: []
}
},
created () {
this.initTableData()
},
methods() {
initTableData () {
db.collection('example').orderBy('owner_name', 'asc').get().then(res => {
res.forEach((doc) => {
this.data.push({
id: doc.id,
})
})
})
},
initTableFiltered () {
db.collection('filter').orderBy('name', 'asc').get().then(res => {
res.forEach((doc) => {
this.data.push({
id: doc.id,
})
})
}
onChangeDistrict () {
this.initTablefiltered()
}
}
So how to initialize the initTablefiltered freshly on onChangeDistrict?
Did you create data variable? This isn't in the code?
Ex:
export default {
data() {
return {
data: []
}
},
...
Without instantiate your data, this is not reactive...

Yields "TypeError: Cannot read property 'xxxx' of undefined" after running jest with Vue

I'm trying to make a test using jest with Vue.
the details below.
Problem:
Can't mount using shallowMount option.
Situation:
Run the test after mounting the component using shallowMount option that provides in Vue-test-utils.
Throw error "Cannot read property 'XXXX' of undefined
This is my test code.
import myComponent from '#/~';
import Vuex from 'vuex';
import Vuelidate from 'vuelidate';
import { shallowMount, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuelidate);
describe('myComponent~', () => {
let store;
beforeEach(() => {
store = new Vuex.Store({
modules: {
user: {
namespaced: true,
getters: {
profile: () => {
const profile = { name: 'blahblah' };
return profile;
},
},
},
},
});
});
describe('profile.name is "blahblah"', () => {
it('return something~', () => {
const wrapper = shallowMount(myComponent, {
localVue,
store,
mocks: {
$api: {
options: {
testMethod() {
return new Promise((resolve, reject) => {
resolve('test');
});
},
},
},
$i18n: {
t() {
return {
EN: 'EN',
KO: 'KO',
JP: 'JA',
SC: 'zh-CN',
TC: 'tw-CN',
};
},
},
},
});
expect(wrapper.find('.profile').text()).toBe('blahblah');
});
I think the problem is that property isn't set as a specified value or an empty value like an array or object.
But I don't know how I set properly the properties in my logic.
For example,
when the error yields "Cannot read property 'images' of undefined",
I add to a wrapper in the relevant method like this.
exampleMethod() {
this.something = this.something.map(item => {
if (item.detailContent.images) { // <-- the added wrapper is here
~~~logic~~~~
}
})
}
But the undefined properties are so many, I also think this way is not proper.
How I do solve this problem?
added
These are details about the above example method:
exampleMethod() {
this.something = this.something.map(item => {
let passValidation = false;
let failValidation = false;
if (item.detailContent.images) {
if (this.detail.showLanguages.includes(item.code)) {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: true,
}
});
}
checkValidationPass = true;
} else {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: false,
}
});
}
checkValidationPass = false;
}
return {
...item,
required: passValidation,
warning: failValidation,
}
}
});
if (this.configId === 'OPTION2') {
this.checkOption2Validation();
} else if (this.configId === 'OPTION3') {
this.checkOption3Validation();
} else {
this.checkOption1Validation();
}
},
And this is 'this.something':
data() {
return {
something: []
}
}
The detailContent is set here.
setMethod() {
this.something = [
...this.otherthings,
];
this.something = this.something.map(item => {
let details1 = {};
if (this.configId === 'OPTION2') {
details1 = {
images: [
{ deviceType: 'PC', titleList: [null, null], imageType: 'IMAGE' },
{ deviceType: 'MOBILE', titleList: [null, null, null] }
]
};
} else if (this.configId === 'OPTION3') {
details1 = {
images: [
{ deviceType: 'PC' },
{ deviceType: 'MOBILE' }
],
links: { linkType: 'EMPTY' },
};
}
let details2 = {
mainTitle: {
content: null,
}
}
let checkValidation = false;
this.detail.detailLanguages.forEach(element => {
if (element.language === item.code) {
details1 = { ...element };
if (!!element.mainTitle) {
details2 = { ...element };
} else {
details2 = {
...details2,
...element
};
}
if (this.configId !== 'OPTION1') {
details1.images = details1.images.map(image => {
return {
...image,
required: true,
}
});
}
checkValidation = true;
}
});
return {
...item,
detailContent: this.configId !== 'OPTION1' ? details1 : details2,
required: false,
warning: false,
}
});
},

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');
});
});

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))
})
})
})

GraphQL Validation Error on mutation

I am trying to set up a mutation with modified code from the Relay Todo example.
When I try to compile I get the following error:
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Cannot query field "addCampaign" on type "Mutation".
Source:
>
> mutation AddCampaignMutation {addCampaign}
> ^^^
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Unknown type "AddCampaignPayload". Did you mean "AddTodoPayload" or "RenameTodoPayload"?
Source:
>
> fragment AddCampaignMutationRelayQL on AddCampaignPayload #relay(pattern: true) {
> ^^
I have duplicated the Todo code so I don't know why the Todo mutation is working correctly but my new Campaign test isn't.
This is my database.js file, I have removed the Todo related items to make the document easier to read:
export class Campaign {}
export class User {}
// Mock authenticated ID
const VIEWER_ID = 'me';
// Mock user data
const viewer = new User();
viewer.id = VIEWER_ID;
const usersById = {
[VIEWER_ID]: viewer,
};
// Mock campaign data
const campaignsById = {};
const campaignIdsByUser = {
[VIEWER_ID]: [],
};
let nextCampaignId = 0;
addCampaign('Campaign1');
addCampaign('Campaign2');
addCampaign('Campaign3');
addCampaign('Campaign4');
export function addCampaign(text) {
const campaign = new Campaign();
//campaign.complete = !!complete;
campaign.id = `${nextCampaignId++}`;
campaign.text = text;
campaignsById[campaign.id] = campaign;
campaignIdsByUser[VIEWER_ID].push(campaign.id);
return campaign.id;
}
export function getCampaign(id) {
return campaignsById[id];
}
export function getCampaigns(status = 'any') {
const campaigns = campaignIdsByUser[VIEWER_ID].map(id => campaignsById[id]);
if (status === 'any') {
return campaigns;
}
return campaigns.filter(campaign => campaign.complete === (status === 'completed'));
}
This is my schema.js file, again I have removed the Todo related items to make the document easier to read:
import {
GraphQLBoolean,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
cursorForObjectInConnection,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
nodeDefinitions,
toGlobalId,
} from 'graphql-relay';
import {
Campaign,
addCampaign,
getCampaign,
getCampaigns,
User,
getViewer,
} from './database';
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'User') {
return getUser(id);
} else if (type === 'Campaign') {
return getCampaign(id);
}
return null;
},
(obj) => {
if (obj instanceof User) {
return GraphQLUser;
} else if (obj instanceof Campaign) {
return GraphQLCampaign;
}
return null;
}
);
/**
* Define your own connection types here
*/
const GraphQLAddCampaignMutation = mutationWithClientMutationId({
name: 'AddCampaign',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
campaignEdge: {
type: GraphQLCampaignEdge,
resolve: ({localCampaignId}) => {
const campaign = getCampaign(localCampaignId);
return {
cursor: cursorForObjectInConnection(getCampaigns(), campaign),
node: campaign,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localCampaignId = addCampaign(text);
return {localCampaignId};
},
});
const GraphQLCampaign = new GraphQLObjectType({
name: 'Campaign',
description: 'Campaign integrated in our starter kit',
fields: () => ({
id: globalIdField('Campaign'),
text: {
type: GraphQLString,
description: 'Name of the campaign',
resolve: (obj) => obj.text,
}
}),
interfaces: [nodeInterface]
});
const {
connectionType: CampaignsConnection,
edgeType: GraphQLCampaignEdge,
} = connectionDefinitions({
name: 'Campaign',
nodeType: GraphQLCampaign,
});
const GraphQLUser = new GraphQLObjectType({
name: 'User',
fields: {
id: globalIdField('User'),
campaigns: {
type: CampaignsConnection,
args: {
...connectionArgs,
},
resolve: (obj, {...args}) =>
connectionFromArray(getCampaigns(), args),
},
totalCount: {
type: GraphQLInt,
resolve: () => getTodos().length,
},
completedCount: {
type: GraphQLInt,
resolve: () => getTodos('completed').length,
},
},
interfaces: [nodeInterface],
});
const Root = new GraphQLObjectType({
name: 'Root',
fields: {
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
node: nodeField,
},
});
This is my AddCampaignMutation.js file:
import Relay from 'react-relay';
export default class AddCampaignMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
console.log('getMutation');
return Relay.QL`mutation{addCampaign}`;
}
getFatQuery() {
console.log('getFatQuery');
return Relay.QL`
fragment on AddCampaignPayload #relay(pattern: true) {
campaignEdge,
viewer {
campaigns,
},
}
`;
}
getConfigs() {
console.log('getConfigs');
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'campaigns',
edgeName: 'campaignEdge',
rangeBehaviors: ({orderby}) => {
if (orderby === 'newest') {
return 'prepend';
} else {
return 'append';
}
},
//rangeBehaviors: ({status}) => {
// if (status === 'completed') {
// return 'ignore';
// } else {
// return 'append';
// }
//},
}];
}
getVariables() {
console.log('getVariables');
return {
text: this.props.text,
};
}
getOptimisticResponse() {
console.log('getOptimisticResponse');
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
campaignEdge: {
node: {
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
And finally this is the app file that contains my text input and the call to AddCampaignMutation:
import AddTodoMutation from '../mutations/AddTodoMutation';
import AddCampaignMutation from '../mutations/AddCampaignMutation';
import TodoListFooter from './TodoListFooter';
import TodoTextInput from './TodoTextInput';
import React from 'react';
import Relay from 'react-relay';
class TodoApp extends React.Component {
_handleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddTodoMutation({text, viewer: this.props.viewer})
);
};
_campaignHandleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddCampaignMutation({text, viewer: this.props.viewer})
);
};
render() {
const hasTodos = this.props.viewer.totalCount > 0;
return (
<div>
<section className="todoapp">
<header className="header">
<TodoTextInput
autoFocus={true}
className="new-campaign"
onSave={this._campaignHandleTextInputSave}
placeholder="Campaign name"
/>
<h1>
todos
</h1>
<TodoTextInput
autoFocus={true}
className="new-todo"
onSave={this._handleTextInputSave}
placeholder="What needs to be done?"
/>
</header>
{this.props.children}
{hasTodos &&
<TodoListFooter
todos={this.props.viewer.todos}
viewer={this.props.viewer}
/>
}
</section>
</div>
);
}
}
export default Relay.createContainer(TodoApp, {
fragments: {
viewer: () => Relay.QL`
fragment on User {
totalCount,
${AddTodoMutation.getFragment('viewer')},
${AddCampaignMutation.getFragment('viewer')},
${TodoListFooter.getFragment('viewer')},
}
`,
},
});
Well I feel a bit silly but I found out what the problem was. I didn't realise that my schema.json file was not updating!
If anyone has a similar problem make sure that the schema.json file is up to date by running the following command to rebuild it:
npm run-script update-schema

Categories

Resources