Expected false to be true when test react component with jasmine jsdom and enzyme - unit-testing

When I ran the jasmine as a gulp task, the test seems runs well, though first one is always considered failed. I am not sure where the problem is.
React Component
import React, { PropTypes } from 'react';
const propTypes = {};
const defaultProps = {};
class Foo extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="foo"> </div>
);
}
}
Foo.propTypes = propTypes;
Foo.defaultProps = defaultProps;
export default Foo;
Spec File
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Foo from './foo.react';
import jsDom from 'jsdom';
global.document = jsDom.jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
describe("A suite", function() {
it("contains spec with an expectation", function() {
console.log(shallow(<Foo />));
expect(shallow(<Foo />).contains(<div className="foo" />)).toBe(true);//.toBe(true)
});
it("contains spec with an expectation", function() {
expect(shallow(<Foo />).is('.foo')).toBe(true);
});
it("contains spec with an expectation", function() {
expect(mount(<Foo />).find('.foo').length).toBe(1);
});
});
Result

Found out I have to change my return to
<div className="foo" /> instead of <div className="foo"> </div>
Then the test will pass.
Do not yet understand why they are different still though.

I think the issue may be in the white space you have in <div className="foo"> </div>
contains checks for an exact match, and an empty tag is different than the same tag containing a space characters. See also this discussion for a possible workaround involving elem.html()

Related

Best way to mock/stub vue-i18n translations in a vue3 component when using Vitest

I have started to replace Jest with Vitest for my unit test library in my Vue 3 App.
I am trying to write unit test for a component that uses the vue-i18n library to translate text within it but when I try to mount this component in my test file, it fails with the error:
ReferenceError: t is not defined
What is the proper way to stub/mock t from import { useI18n } from 'vue-i18n' when writing tests using the vitest library?
Note since upgrading from Vue2 to Vue3 this does not work:
const wrapper = shallowMount(MyComponent, {
global: {
mocks: {
$t: () => {}
}
}
})
Here is a list of some notable package versions:
"vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.14",
"vite": "^2.9.0",
"vitest": "^0.10.2"
Thanks!
import { createI18n } from 'vue-i18n';
describe('xxx', () => {
it('yyy', () => {
const i18n = createI18n({
messages: {
gb: {},
nl: {},
...
}
});
const wrapper = mount(YourComponent, {
global: {
plugins: [i18n]
}
});
}
})
I suppose you want to mock this globally, no need to put same code in every test suite.
// vitest.config.ts
import { mergeConfig } from 'vite';
import { defineConfig } from 'vitest/config';
import viteConfig from './vite.config';
export default defineConfig(
mergeConfig(viteConfig, { // extending app vite config
test: {
setupFiles: ['tests/unit.setup.ts'],
environment: 'jsdom',
}
})
);
// tests/unit.setup.ts
import { config } from "#vue/test-utils"
config.global.mocks = {
$t: tKey => tKey; // just return translation key
};
Panos Vakalopoulos’s answer worked for me.
And the code could be run globally.
See https://test-utils.vuejs.org/migration/#no-more-createlocalvue
// vite.config.ts
export default defineConfig(
// add config for test
test: {
environment: 'jsdom',
setupFiles: 'vitest.setup.ts',
}
);
// vitest.setup.ts'
import { config } from '#vue/test-utils'
import { createI18n } from 'vue-i18n'
const i18n = createI18n()
config.global.plugins = [i18n]
// YourComponent.vue
<div id="app">
<p>{{ t("message.hello") }}</p>
</div>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
// component_test.ts
describe('xxx', () => {
it('yyy', () => {
const wrapper = mount(YourComponent);
}
})
Note that if you use global config as $t, Luckylooke's answer would work.
// YourComponent.vue
<div id="app">
<p>{{ $t("message.hello") }}</p>
</div>
// tests/unit.setup.ts
import { config } from "#vue/test-utils"
config.global.mocks = {
$t: tKey => tKey; // just return translation key
};
I read this tutorial that teaches to mock vue-router, then I made a similar solution for vue-i18n and it worked.
Component (HelloWorld.vue)
<script setup>
import { useI18n } from "vue-i18n";
const { t } = useI18n();
</script>
<template>
<div class="greetings">
<h1>{{ t("commonsmessagehello") }}</h1>
<h2>{{ t("localhello") }}</h2>
<h2>{{ $t("message.success") }}</h2>
</div>
</template>
<i18n src="../commons/locales.json"></i18n>
<i18n>
{
"enUS": {
"localhello": "local helloooooo"
}
}
</i18n>
Test
import { describe, it, expect, vi } from "vitest";
import { mount, config } from "#vue/test-utils";
import { useI18n } from "vue-i18n";
import HelloWorld from "../HelloWorld.vue";
vi.mock("vue-i18n");
useI18n.mockReturnValue({
t: (tKey) => tKey,
});
config.global.mocks = {
$t: (tKey) => tKey,
};
describe("HelloWorld", () => {
it("renders properly", () => {
const wrapper = mount(HelloWorld, { });
expect(wrapper.text()).toContain("message.success");
});
});
How you can see, it worked for t and $t.
That's not the ideal way. Someday I'll try to figure out how to do it globally for every test.

Testing VueJS component containing other components written by myself

I work on a website with multiple components that contain other components. Now I would like to test if the save button of a form is deactivated correctly if no data is set. I am using vuetify for the UI and Jest for testing.
Here is my parent component, containing the edit-user-details component:
<template>
<v-container>
<v-form v-model="valid">
<v-card>
<v-card-text>
<edit-user-details :user="user"></edit-user-details>
</v-card-text>
<v-card-actions>
<v-btn :disabled="!valid" #click="save()">Save</v-btn>
<v-btn #click="cancel()">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-container>
</template>
<script>
export default {
name: "edit-user",
components: {},
data: () => ({
user: {},
valid: false
}),
methods: {
save() {
...
},
cancel() {
...}
}
}
}
</script>
This is a part of the edit-user-details component:
<template>
<v-container>
<v-text-field
v-model="user.userName"
label="Username*"
required
:rules="[v => !!v || 'Please, enter a user name.']"
></v-text-field>
...
</v-container>
</template>
<script>
export default {
name: "edit-user-details",
props: {
user: {
type: Object,
default: () => ({})
}
}
}
</script>
And here we have my test:
import { mount, createLocalVue } from '#vue/test-utils'
import EditUser from '../../src/views/EditUser'
import Vuetify from 'vuetify';
import EditUserDetails from '../../src/components/EditUserDetails'
describe('Edited user data ', () => {
it('can be saved if valid', () => {
const localVue = createLocalVue();
localVue.use(Vuetify)
localVue.use(EditUserDetails)
const wrapper = mount(EditUser, {
localVue: localVue
});
})
})
The test is green because it has no assert. The main issue is, that I get this error: [Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
So my question is: How can I test a component containing other components written by me?
Thank you in advance for your help.
instead of mount, use shallowMount.
Like mount, it creates a Wrapper that contains the mounted and
rendered Vue component, but with stubbed child components.
https://vue-test-utils.vuejs.org/api/#shallowmount
I haven’t tried this together with createLocalVue(), but I hope it'll help:
import Vuetify from 'vuetify'
const vuetify = new Vuetify()
const wrapper = mount(Component, { ..., vuetify })

How come this test fails?

I'm new to unit testing with jest & enzyme.
I want to test if the component has a class name 'comment-box' or not.
In the component I conduct a unit test, I do have a div with class name 'comment-box'.
But, when I run a test, it fails.
Probably, Im making an easy mistake since Im new to jest & enzyme.
Could anyone please help me to find out the problem?
Thanks!
Log in my test runner.
FAIL src/__tests__/components/CommentBox.test.js
● CommentBox › has the right class
expect(received).toBe(expected)
Expected value to be (using ===):
true
Received:
false
CommentBox.js
import React, { Component } from 'react';
class CommentBox extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div class="comment-box">
<textarea></textarea>
<button>Submit</button>
</div>
)
}
}
export default CommentBox;
CommentBox.test.js
import React, { Component } from 'react';
import { shallow, mount, render } from 'enzyme';
import CommentBox from '../../components/CommentBox';
jest.unmock('../../components/CommentBox');
describe('CommentBox', () => {
it('has the correct class', () => {
const component = shallow(<CommentBox />);
expect(component.find('div').hasClass('comment-box')).toBe(true);
// I tried this one as well.
// expect(component.find('div').first().hasClass('comment-box')).toBe(true);
});
});
It should be
<div className="comment-box">

How to unit test a method of react component?

I am trying to unit test my reactjs component:
import React from 'react';
import Modal from 'react-modal';
import store from '../../../store'
import lodash from 'lodash'
export class AddToOrder extends React.Component {
constructor(props) {
super(props);
this.state = {checked: false}
//debugger
}
checkBoxChecked() {
return true
}
render() {
console.log('testing=this.props.id',this.props.id )
return (
<div className="order">
<label>
<input
id={this.props.parent}
checked={this.checkBoxChecked()}
onChange={this.addToOrder.bind(this, this.props)}
type="checkbox"/>
Add to order
</label>
</div>
)
}
}
export default AddToOrder;
Just to get started I am already struggling to assert the checkBoxChecked method:
import React from 'react-native';
import {shallow} from 'enzyme';
import {AddToOrder} from '../app/components/buttons/addtoorder/addtoorder';
import {expect} from 'chai';
import {mount} from 'enzyme';
import jsdom from 'jsdom';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
let props;
beforeEach(() => {
props = {
cart: {
items: [{
id: 100,
price: 2000,
name:'Docs'
}]
}
};
});
describe('AddToOrder component', () => {
it('should be handling checkboxChecked', () => {
const wrapper = shallow(<AddToOrder {...props.cart} />);
expect(wrapper.checkBoxChecked()).equals(true); //error appears here
});
});
```
How can I unit test a method on the component? This is the error I am getting:
TypeError: Cannot read property 'checked' of undefined
You are almost there. Just change your expect to this:
expect(wrapper.instance().checkBoxChecked()).equals(true);
You can go through this link to know more about testing component methods using enzyme
For those who find the accepted answer as not working, try using .dive() on your shallow wrapper before using .instance():
expect(wrapper.dive().instance().somePrivateMethod()).toEqual(true);
Reference: Testing component methods with enzyme
Extend of previous answer.
If you have connected component (Redux) , try next code :
const store=configureStore();
const context = { store };
const wrapper = shallow(
<MyComponent,
{ context },
);
const inst = wrapper.dive().instance();
inst.myCustomMethod('hello');

Vue.js filters and testing

I used vue-cli to create sample project vue init webpack my-test3, and opted for including both e2e and unit tests.
Question 1: Based on documentation for template filters I tried to add new filter as such in main.js:
import Vue from 'vue'
import App from './App'
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App },
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
And my App.vue:
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
<world></world>
<div class="test-test">{{ 'tesT' | capitalize }}</div>
</div>
</template>
<script>
import Hello from './components/Hello'
import World from './components/World'
export default {
name: 'app',
components: {
Hello,
World
}
}
</script>
I get warning(error):
[Vue warn]: Failed to resolve filter: capitalize (found in component <app>)
If I modify main.js to register filter before initializing new Vue app, then it works without problem.
import Vue from 'vue'
import App from './App'
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Why one works and other not?
Question 2: With example above that works Vue.filter(..., I added a test for World.vue component:
<template>
<div class="world">
<h1>{{ msg | capitalize }}</h1>
<h2>{{ desc }}</h2>
Items (<span>{{ items.length }}</span>)
<ul v-for="item in items">
<li>{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'world',
data () {
return {
msg: 'worldly App',
desc: 'This is world description.',
items: [ 'Mon', 'Wed', 'Fri', 'Sun' ]
}
}
}
</script>
And World.spec.js:
import Vue from 'vue'
import World from 'src/components/World'
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
describe('World.vue', () => {
it('should render correct title', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(World)
})
expect(vm.$el.querySelector('.world h1').textContent)
.to.equal('Worldly App')
})
it('should render correct description', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (w) => w(World)
})
expect(vm.$el.querySelector('.world h2').textContent)
.to.equal('This is world description.')
})
})
For the above test to pass, I need to include Vue.filter(... definition for filter capitalize, otherwise tests would fail. So my question is, how to structure filters/components and initialize them, so testing is easier?
I feel like I should not have to register filters in unit tests, that should be part of the component initialization. But if component is inheriting/using filter defined from main app, testing component will not work.
Any suggestions, comments, reading materials?
A good practice is create a filters folder and define your filters inside this folder in individual files and define all your filters globally if you want to have access to them in all of your components, eg:
// capitalize.js
export default function capitalize(value) {
if (!value) return '';
value = value.toString().toLowerCase();
value = /\.\s/.test(value) ? value.split('. ') : [value];
value.forEach((part, index) => {
let firstLetter = 0;
part = part.trim();
firstLetter = part.split('').findIndex(letter => /[a-zA-Z]/.test(letter));
value[index] = part.split('');
value[index][firstLetter] = value[index][firstLetter].toUpperCase();
value[index] = value[index].join('');
});
return value.join('. ').trim();
}
To test it successfully and if you use #vue/test-utils, to test your single file components, you can do that with createLocalVue, like this:
// your-component.spec.js
import { createLocalVue, shallowMount } from '#vue/test-utils';
import capitalize from '../path/to/your/filter/capitalize.js';
import YOUR_COMPONENT from '../path/to/your/component.vue';
describe('Describe YOUR_COMPONENT component', () => {
const localVue = createLocalVue();
// this is the key line
localVue.filter('capitalize', capitalize);
const wrapper = shallowMount(YOUR_COMPONENT, {
// here you can define the required data and mocks
localVue,
propsData: {
yourProp: 'value of your prop',
},
mocks: {
// here you can define all your mocks
// like translate function and others.
},
});
it('passes the sanity check and creates a wrapper', () => {
expect(wrapper.isVueInstance()).toBe(true);
});
});
I hope to help you.
Regards.
My best guess is that if you define filters inside a component, they will be only available for use inside this component. In your case, you can only use capitalize in the main Vue instance, not its child components. Moving this to a global level solves the issue.
For the second question, you are doing the right thing by adding the filter definition in the testing file.