Cannot read property $loading of undefined when using Jest with Nuxt component - unit-testing

When I try to test one of my components using nuxt and jest, I get the following error:
Cannot read property '$loading' of undefined
This is being caused by the following line of code in my component
this.$nuxt.$loading.start()
How do I prevent this error from occurring when running the test on my component?
The test file looks like this:
import { mount } from '#vue/test-utils'
import Converter from '#/components/Converter.vue'
describe('Converter', () => {
test('is a Vue instance', () => {
const wrapper = mount(Converter)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})

I found a solution. The solution is to mock nuxt like this:
const wrapper = mount(Converter, {
mocks: {
$nuxt: {
$loading: {
start: () => {}
}
}
}
})

Related

Vitest gives no output

I'm new to testing and im trying to write some unit tests for my Vue app. The problem is that vitest givesno output and I cant figure out what is wrong. Any help would be apriciated.
describe('UserForm', () => {
it('renders component properly', async () => {
const viewId = "123"
render(UserForm, {
props: {
open: true
}
})
const view = await screen.findByText('Kontrahent')
expect(view.id).toBe(viewId)
})
})
I run the test with this command
vitest --environment jsdom
Have you tried to do something like:
import { mount } from "#vue/test-utils";
// in the describe
const wrapper = mount(Login, {
props: {
open: true
},
});
it("mounts the component", () => {
expect(wrapper.html()).toContain("Kontrahent");
});

How to stub Vue component methods for unit testing

How can I stub certain methods (getters, in particular) from Vue single file components for unit testing with mocha/expect?
The problem I was facing was the following:
I have a component with a get method someData
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import SomeService from '#/services/some.service'
#Component()
export default class MyApp extends Vue {
...
mounted () {
...
}
get someData () {
return this.$route.path.split('/')[1] || 'main'
}
get getLocation () {
return this.someService.getBaseURL()
}
initSomeStringProperty (): string {
return 'some string'
}
}
</script>
My tests always fail with:
[Vue warn]: Error in render: "TypeError: Cannot read property 'path' of undefined"
When I try to stub the method using sinon, like following:
describe('MyApp.vue', () => {
if('returns main', () => {
const dataStub = sinon.stub(MyApp, 'someData')
listStub.yields(undefined, 'main')
const wrapper = shallowMount(AppNavBar)
expect(wrapper.text()).to.include('Some Content')
})
})
However, I get the following error:
TypeError: Cannot stub non-existent own property someData
In addition, I get the same error for every other method, I want to stub analogously, e.g., initSomeStringProperty().
In the code above someData is computed property that is defined with property accessor through vue-property-decorator.
It can be stubbed at two points, either on class prototype:
sinon.stub(MyApp.prototype, 'someData').get(() => 'foo');
Or component options:
sinon.stub(MyApp.options.computed.someData, 'get').value('foo');
You could set the component's computed props and methods upon mounting, as shown below. Update: As of 1.x, setting methods has been deprecated in favor of stubbing (see #EstusFlask's answer on how to properly stub with Sinon).
const wrapper = shallowMount(MyApp, {
computed: {
someData: () => 'foo'
},
methods: {
initSomeStringProperty: () => 'bar'
}
})
expect(wrapper.vm.someData).to.equal('foo')
expect(wrapper.vm.initSomeStringProperty()).to.equal('bar')
If you were just trying to avoid the error about $route being undefined, you could mock $route upon mounting:
const wrapper = shallowMount(MyApp, {
mocks: {
$route: { path: '/home' }
}
})
expect(wrapper.vm.someData).to.equal('home')

Unit test vue.js component with inline-template

I am using Vue 2 to enhance a Ruby on Rails engine, using inline-template attributes in the existing Haml views as templates for my Vue components.
Is it possible to test the methods of a component defined like this? All the testing examples I can find assume the use of single-file .vue components.
These tests (using Mocha and Chai) fail with [Vue warn]: Failed to mount component: template or render function not defined.
Example Component:
//main-nav.js
import Vue from 'vue'
const MainNav = {
data: function() {
return {open: true}
},
methods: {
toggleOpen: function(item) {
item.open = !item.open
}
}
}
export default MainNav
Example Test:
//main-nav.test.js
import MainNav from '../../admin/main-nav'
describe('MainNav', () => {
let Constructor
let vm
beforeEach(() => {
Constructor = Vue.extend(MainNav)
vm = new Constructor().$mount()
})
afterEach(() => {
vm.$destroy()
})
describe('toggleOpen', () => {
it('has a toggleOpen function', () => {
expect(vm.MainNav.toggleOpen).to.be.a('function')
})
it('toggles open from true to false', () => {
const result = MainNav.toggleOpen({'open': true})
expect(result).to.include({open: false})
})
})
})
It turns out you can still specify a template in the component file, and any inline-template templates will be used in favour of that.

Jest -- Mock a function called inside a React Component

Jest provides a way to mock functions as described in their docs
apiGetMethod = jest.fn().mockImplementation(
new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(
() => users[userID] ? resolve(users[userID]) : reject({
error: 'User with ' + userID + ' not found.',
});
);
});
);
However these mocks only seem to work when the function is called directly in a test.
describe('example test', () => {
it('uses the mocked function', () => {
apiGetMethod().then(...);
});
});
If I have a React Component defined as such how can I mock it?
import { apiGetMethod } from './api';
class Foo extends React.Component {
state = {
data: []
}
makeRequest = () => {
apiGetMethod().then(result => {
this.setState({data: result});
});
};
componentDidMount() {
this.makeRequest();
}
render() {
return (
<ul>
{ this.state.data.map((data) => <li>{data}</li>) }
</ul>
)
}
}
I have no idea how to make it so Foo component calls my mocked apiGetMethod() implementation so that I can test that it renders properly with data.
(this is a simplified, contrived example for the sake of understanding how to mock functions called inside react components)
edit: api.js file for clarity
// api.js
import 'whatwg-fetch';
export function apiGetMethod() {
return fetch(url, {...});
}
You have to mock the ./api module like this and import it so you can set the implemenation of the mock
import { apiGetMethod } from './api'
jest.mock('./api', () => ({ apiGetMethod: jest.fn() }))
in your test can set how the mock should work using mockImplementation:
apiGetMethod.mockImplementation(() => Promise.resolve('test1234'))
In case the jest.mock method from #Andreas's answer did not work for you. you could try the following in your test file.
const api = require('./api');
api.apiGetMethod = jest.fn(/* Add custom implementation here.*/);
This should execute your mocked version of the apiGetMethod inside you Foo component.
Here is an updated solution for anyone struggling with this in '21. This solution uses Typescript, so be aware of that. For regular JS just take out the type calls wherever you see them.
You import the function inside your test at the top
import functionToMock from '../api'
Then you indeed mock the call to the folder outside of the tests, to indicate that anything being called from this folder should and will be mocked
[imports are up here]
jest.mock('../api');
[tests are down here]
Next we mock the actual function we're importing. Personally I did this inside the test, but I assume it works just as well outside the test or inside a beforeEach
(functionToMock as jest.Mock).mockResolvedValue(data_that_is_returned);
Now here's the kicker and where everyone seems to get stuck. So far this is correct, but we are missing one important bit when mocking functions inside a component: act. You can read more on it here but essentially we want to wrap our render inside this act. React testing library has it's own version of act. It is also asynchronous, so you have to make sure your test is async and also define the destructured variables from render outside of it.
In the end your test file should look something like this:
import { render, act } from '#testing-library/react';
import UserGrid from '../components/Users/UserGrid';
import { data2 } from '../__fixtures__/data';
import functionToMock from '../api';
jest.mock('../api');
describe("Test Suite", () => {
it('Renders', async () => {
(functionToMock as jest.Mock).mockResolvedValue(data2);
let getAllByTestId: any;
let getByTestId: any;
await act(async () => {
({ getByTestId, getAllByTestId } = render(<UserGrid />));
});
const container = getByTestId('grid-container');
const userBoxes = getAllByTestId('user-box');
});
});
Another solution to mock this would be:
window['getData'] = jest.fn();

How to "mock" navigator.geolocation in a React Jest Test

I'm trying to write tests for a react component I've built that utilizes navigator.geolocation.getCurrentPosition() within a method like so (rough example of my component):
class App extends Component {
constructor() {
...
}
method() {
navigator.geolocation.getCurrentPosition((position) => {
...code...
}
}
render() {
return(...)
}
}
I'm using create-react-app, which includes a test:
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
This test fails, printing out this in the console:
TypeError: Cannot read property 'getCurrentPosition' of undefined
I'm new to React, but have quite a bit of experience with angular 1.x. In angular it is common to mock out (within the tests in a beforeEach) functions, "services", and global object methods like navigator.geolocation.etc. I spent time researching this issue and this bit of code is the closest I could get to a mock:
global.navigator = {
geolocation: {
getCurrentPosition: jest.fn()
}
}
I put this in my test file for App, but it had no effect.
How can I "mock" out this navigator method and get the test to pass?
EDIT: I looked into using a library called geolocation which supposedly wraps navigator.getCurrentPosition for use in a node environment. If I understand correctly, jest runs tests in a node environment and uses JSDOM to mock out things like window. I haven't been able to find much information on JSDOM's support of navigator. The above mentioned library did not work in my react app. Using the specific method getCurrentPosition would only return undefined even though the library itself was imported correctly and available within the context of the App class.
It appears that there is already a global.navigator object and, like you, I wasn't able to reassign it.
I found that mocking the geolocation part and adding it to the existing global.navigator worked for me.
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn()
};
global.navigator.geolocation = mockGeolocation;
I added this to a src/setupTests.js file as described here - https://create-react-app.dev/docs/running-tests#initializing-test-environment
I know this issue might have been solved, but seems that all the solutions above are all wrong, at least for me.
When you do this mock: getCurrentPosition: jest.fn()
it returns undefined, if you want to return something, this is the correct implementation:
const mockGeolocation = {
getCurrentPosition: jest.fn()
.mockImplementationOnce((success) => Promise.resolve(success({
coords: {
latitude: 51.1,
longitude: 45.3
}
})))
};
global.navigator.geolocation = mockGeolocation;
I am using create-react-app
A TypeScript version for anyone that was getting
Cannot assign to 'geolocation' because it is a read-only property.
In the mockNavigatorGeolocation.ts file (this can live in a test-utils folder or similar)
export const mockNavigatorGeolocation = () => {
const clearWatchMock = jest.fn();
const getCurrentPositionMock = jest.fn();
const watchPositionMock = jest.fn();
const geolocation = {
clearWatch: clearWatchMock,
getCurrentPosition: getCurrentPositionMock,
watchPosition: watchPositionMock,
};
Object.defineProperty(global.navigator, 'geolocation', {
value: geolocation,
});
return { clearWatchMock, getCurrentPositionMock, watchPositionMock };
};
I then import this in my test at the top of the file:
import { mockNavigatorGeolocation } from '../../test-utils';
And then use the function like so:
const { getCurrentPositionMock } = mockNavigatorGeolocation();
getCurrentPositionMock.mockImplementation((success, rejected) =>
rejected({
code: '',
message: '',
PERMISSION_DENIED: '',
POSITION_UNAVAILABLE: '',
TIMEOUT: '',
})
);
Mocking with setupFiles
// __mocks__/setup.js
jest.mock('Geolocation', () => {
return {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn(),
}
});
and then in your package.json
"jest": {
"preset": "react-native",
"setupFiles": [
"./__mocks__/setup.js"
]
}
I followed #madeo's comment above to mock global.navigator.geolocation. It worked!
Additionally I did the following to mock global.navigator.permissions:
global.navigator.permissions = {
query: jest
.fn()
.mockImplementationOnce(() => Promise.resolve({ state: 'granted' })),
};
Set state to any of granted, denied, prompt as per requirement.
For whatever reason, I did not have the global.navigator object defined, so I had to specify it in my setupTests.js file
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn(),
}
global.navigator = { geolocation: mockGeolocation }
Added to the above answers, if you want to update navigator.permissions, this will work.The key here is to mark writable as true before mocking
Object.defineProperty(global.navigator, "permissions", {
writable: true,
value: {
query : jest.fn()
.mockImplementation(() => Promise.resolve({ state: 'granted' }))
},
});