Jest unit test fails everytime Vue component gets mounted - vue.js

I am currently trying to write some unit tests for my web application that I made with Nuxt, Vue and Vuetify. I decided to use the Jest framework and the vue-test-utils to write unit tests, but it seems like Jest or vue-test-utils refuses to mount my components.
Example
Let’s take this very simple test where i shallowMount my component and see if the component renders a div:
require("jsdom-global")();
import AlbumHeader from "#/components/Album/AlbumHeader";
import { shallowMount } from "#vue/test-utils";
describe("AlbumHeader.vue", () => {
it("renders a div", () => {
const wrapper = shallowMount(AlbumHeader);
expect(wrapper.contains("div")).toBe(true);
});
});
When I run this unit test it always fails with the error:
TypeError: Cannot read property 'child' of undefined
I feel like the problem has something to do with Vuetify. But even after creating new Vuetify instances in my test and creating a localVue I still cannot get it to pass the test. I feel like I have read every article on this topic and none of them seem to help. This is also the first time I use this framework so I really do not have a clue where this problem takes place.
I hope someone can explain to me what i am doing wrong and how i can fix it, i would really appreciate that.

Related

Why calling async function inside <script> tags fail in Vue if no lifecycle hooks are used?

Here is the scenario, I have a component, and inside the script tag I am calling an action
<script>
if (something is true) {
await store.doSomething()
}
</script>
The component fails to mount.
When I use the onMounted hook, it seems to work.
I am beginner in Vue, but my question is what is really happening when I don't use the hook? and is it always necessary to use hook when making asynchronous calls ?
Put it inside onMounted to get it to work, although ran into other test failures afterwards.
Looking at what you wrote as of right now, you should have the Options API like this
<script>
export default {
mounted() {
// your code
},
setup() {
// can also use setup here
}
}
</script>
With Composition API (notice the setup)
<script setup>
onMounted(() => {
// your code
})
</script>
In 2., if you don't use onMounted it will be run withing the setup lifecycle as shown here.
is it always necessary to use hook when making asynchronous calls ?
Not really, but at the same time it depends on when/how you want it to run. Start by running it into mounted initially yep, easier and safer to understand overall.
Especially since setup does not re-run when re-mounted, can be quite confusing.
It also depends exactly on what is something is true exactly, regarding the lifecycle + state.
Pinia and Vitest will get their own things to think about.
I recommend reading the documentation and getting an initial grasp before proceeding.

Vue Testing Library, Child Component receives props

I'm trying to implement some Testing Library tests on a Vuejs app, but I can't figure out how to pass props to a component within the test.
For example, I want a unit test for a component that appears inside of its ParentComponent template like this. I am trying to write a unit test for the ChildComponent.
<ChildComponent hereIsAProp="important info" />
I'm surprised this scenario isn't covered in the Vue Testing Library basic examples. Makes me think I'm missing some best practice around using/testing Vuejs props.
I imagine something like render(ChildComponent, { props: { hereIsAProp: "new info"}) should do the trick. But I can't find this in the docs and whatnot.
Testing Libary's render() is a wrapper for Vue Test Util's mount().
The second argument to render() is passed onto mount() as mounting options, and mount() can set the component's props with the props option (in version 2x) or propsData (in version 1x).
So your guess is actually correct:
render(ChildComponent, { props: { hereIsAProp: "new info" } })

Snapshot Testing with Vue-Router

I'm trying to run jest-snapshot tests in a Vue app created using the Vue-cli app. When I test a component that have a router-link in it, I receive the following warning.
[Vue warn]: Unknown custom element: <router-link> - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
Following the documentation here Unit tests with View Router I can stub out the router-link, but that doesn't pass my snapshot test. Has anyone run into this issue before?
Updated: A more consistent solution
tl;dr:
The solution I found that works consistently is to create a localVue (usually outside the scope of the describe() blocks) and add the RoutherLinkStub as a component.
import { mount, RouterLinkStub, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.component('router-link', RouterLinkStub);
const wrapper = mount(Component, { localVue });
The documentation doesn't make the solution obvious because it seems to be something of a hybrid of the page you linked to and this one about RouterLinkStub.
From that page:
import { mount, RouterLinkStub } from '#vue/test-utils'
const wrapper = mount(Component, {
stubs: {
RouterLink: RouterLinkStub
}
})
That solution works in most cases. If you have a case where you need to use mount and your router-link is a not in the component being tested but the one below it, you will still get that warning. Now, if you are in that situation, it's worth reflecting if you're writing your test properly (are you testing too much rather than a small unit?), but I have a situation or two where if I shallow instead of mount, the snapshot test is worthless because it renders children as <!----> when I callexpect(wrapper.html()).toMatchSnapshot()`.
A Note on Snapshot Tests:
I haven't actually seen examples of people using the Wrapper.html() method for snapshot testing, but it really seems like the best solution in my experimenting. vue-server-renderer is a long walk for what appears to be the same result. Accessing the vm property of Wrapper gives you access to the $el property, without much trouble, but it's just a more whitespace-heavy rendering of html().

Custom js library(scrollMonitor) inside main Vue instance to be shared with inner components

This is Vue.js question, generally I'm trying to use 'scrollMonitor' function inside of my .vue instance(imported via main.js) but it gives me a typical 'this.scrollMonitor is not a function' error
mounted () {
let watcher = this.$scrollMonitor(this.$refs.nicer)
}
In main.js ScrollMonitor library seems to be properly imported(console shows what's expected):
import scrollMonitor from 'scrollmonitor'
Vue.use(scrollMonitor)
console.log(scrollMonitor)
Again main goal is using scrollMonitor functionality inside of .vue file(in vue component instance). Sorry if I'm missing something silly here - I'm already using some other libraries like Vue-Resource in that file so issue is not in 'filepath' but rather in the way I'm using scrollMonitor functionality, any help is much appreciated, thank you !
For those who are still looking: there is a way of adding plain js libraries to the main.js and then using them with ease globally in inner components(this is not about mixins):
import scrollmonitor from 'scrollmonitor'
Object.defineProperty(Vue.prototype, '$scrollmonitor', {
get() {return this.$root.scrollmonitor}
})
also it should be added to main Vue data object:
data () {
return { scrollmonitor }
},
And then it can be used within mounted() callback (not created() one) inside of the component itself, with scrollmonitor it may look like this(in my specific case the template had a div with ref="nicer" attribute, 'create' is a method specific to the library api):
mounted () {
this.$scrollmonitor.create(this.$refs.nicer)
}
Hooray, I hope someone may find this useful as I did!
Are you using a plain javascript library and trying to Vue.use it? That won't really work. Vue.use will only work with plugins designed to work with Vue. Import the library into the component that needs and and just use it there.
scrollMonitor(this.$refs.nicer)

Using Mocha to test higher order components in React

I am using a HOC in a React component that looks like:
import React from 'react';
import Wrapper from 'wrapper';
class Component extends React.Component {
render () {
return (
<div className='component' />
)
};
}
export default Wrapper(Component)
When testing Component using Mocha I am attempting to look for a class name that should be contained within Component. Like so:
describe('Component', function () {
it('can be mounted with the required class', function () {
const component = shallow(
<Component />
);
expect(component).to.have.className('component');
});
});
The problem is that Mocha doesn't know to look within the wrapper for the Component and attempts to find it in the HOC. Which of course it will not.
The error I am receiving is:
AssertionError: expected <Wrapper(Component) /> to have a 'component' class, but it has undefined
HTML:
<div class="component">
</div>
How do I tell Mocha to look within the HOC for the correct location of the class name instead of the HOC itself?
You can use enzyme .dive()
const component = shallow(<Component />).dive();
expect(component.props().className).toEqual('component')
The problem is the use of Enzyme's shallow instead of mount, which is required when testing HOC's.
So, use mount.
I added this to a github project so you can see. Use my redux-form-test project and be sure to use the stackoverflow-question-38106763 branch. See the tests/unit/index.js file.
Be sure to read the source code of the test file. One of tests fails intentionally to reproduce your issue.
What's tricky in this situation is that the render method of the HOC exactly replicates the component it's wrapping. See the render method in the source of the react-onclickoutside component you mention. That's why the AssertionError you see is confusing: it shows you HTML that looks like it's satisfying the assertion. However, if you run
console.log(component.debug())
before your expect, it'll show
<Component />
That's because shallow only goes one level deep. And enzyme is not using the rendered HTML for the assertion, it's using the React elements, which does not have the component class on it, as you can see.