I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.
I have the following unit code which isn't working as intended:
it('On route enter, it should dispatch an action to fetch form details', () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
wrapper.vm.$options.beforeRouteEnter[0]();
expect(getFormDetails.called).to.be.true;
});
With the following component (stripped of everything because I don't think its relevant (hopefully):
export default {
async beforeRouteEnter(to, from, next) {
await store.dispatch('getFormDetails');
next();
}
};
I get the following assertion error:
AssertionError: expected false to be true
I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.
Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.
I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽
See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards
Based on the doc, your test should look like this:
it('On route enter, it should dispatch an action to fetch form details', async () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
const next = sinon.stub()
MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)
await wrapper.vm.$nextTick()
expect(getFormDetails.called).to.be.true;
expect(next.called).to.be.true
});
A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:
// mount the component
const wrapper = mount(Component, {});
// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));
// await
await wrapper.vm.$nextTick();
Related
How might a Flow.js interface be mocked with Jest? To my surprise, I haven't found this issue addressed anywhere.
I'm fairly new to both, but the only (untested) option I see is to create a class that inherits from the interface and then mock the implementing class. This seems quite cumbersome and I don't believe I could place the implementing classes (which are what would actually be mocked) inside the __mocks__ folders expected by Jest and still get the expected behavior.
Any suggestions? Is there a more appropriate mocking tool?
Update
Why do I want to create a mock for an interface? This code intends to have a clean separation of the domain and implementation layers, with the domain classes using Flow interfaces for all injected dependencies. I want to test these domain classes. Using a mocking tool could ideally allow me to more easily and expressively modify the behavior of the mocked services and confirm that the domain class being tested is making the appropriate calls to these mocked services.
Here's a simplified example of a class that I would be testing in this scenario. UpdateResources would be the class under test, while ResourceServer and ResourceRepository are interfaces for services that I would like to mock and 'spy' upon:
// #flow
import type { ResourceServer } from '../ResourceServer';
import type { ResourceRepository } from '../ResourceRepository';
/**
* Use case for updating resources
*/
export default class UpdateResources {
resourceServer: ResourceServer;
resourceRepository: ResourceRepository;
constructor(resourceServer: ResourceServer, resourceRepository: ResourceRepository) {
this.resourceServer = resourceServer;
this.resourceRepository = resourceRepository;
}
async execute(): Promise<boolean> {
const updatesAvailable = await this.resourceServer.checkForUpdates();
if (updatesAvailable) {
const resources = await this.resourceServer.getResources();
await this.resourceRepository.saveAll(resources);
}
return updatesAvailable;
}
}
A solution
The approach I've arrived at which seems to work quite well for my purposes is to create a mock implementation of the interface in the __mocks__ directory what exposes jest.fn objects for all implemented methods. I then instantiate these mock implementations with new and skip any use of jest.mock().
__mocks__/MockResourceServer.js
import type { ResourceServer } from '../ResourceServer';
export default class MockResourceServer implements ResourceServer {
getResources = jest.fn(() => Promise.resolve({}));
checkForUpodates = jest.fn(() => Promise.resolve(true));
}
__mocks__/MockResourceRepository.js
import type { ResourceRepository } from '../ResourceRepository';
export default class MockResourceRepository implements ResourceRepository {
saveAll = jest.fn(() => Promise.resolve());
}
__tests__/UpdateResources.test.js
import UpdateResources from '../UpdateResources';
import MockResourceRepository from '../../__mocks__/MockResourceRepository';
import MockResourceServer from '../../__mocks__/MockResourceServer';
describe('UpdateResources', () => {
describe('execute()', () => {
const mockResourceServer = new MockResourceServer();
const mockResourceRepository = new MockResourceRepository();
beforeEach(() => {
jest.clearAllMocks();
});
it('should check the ResourceServer for updates', async () => {
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceServer.checkForUpdates).toHaveBeenCalledTimes(1);
});
it('should save to ResourceRepository if updates are available', async () => {
mockResourceServer.load.mockResolvedValue(true);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).toHaveBeenCalledTimes(1);
});
it('should NOT save to ResourceRepository if NO updates are available', async () => {
mockResourceServer.load.mockResolvedValue(false);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).not.toHaveBeenCalled();
});
});
});
If anyone can offer any improvements, I'm open!
The thing is, you don't actually need to mock an implementation of an interface. The purpose of a mock is to 'look like' the real thing, but if you already have an interface that says what the real thing should look like, any implementation that conforms to the interface will automatically serve equally well as a mock. In fact, from the point of view of the typechecker, there won't be a different between the 'real' and the 'mock' implementation.
Personally what I like to do is to create a mock implementation that can be constructed by feeding it mock responses. Then it can be reused in any test case by constructing it directly in that test case with the exact responses it should provide. I.e., you 'script' the mock with what it should say by injecting the responses at the time of construction. The difference between it and your mocking implementation is that if it doesn't have a response, it throws a exception and fails the test. Here's an article I wrote that shows this method: https://dev.to/yawaramin/interfaces-for-scaling-and-testing-javascript-1daj
With this technique, a test case might look like this:
it('should save to ResourceRepository if updates are available', async () => {
const updateResources = new UpdateResources(
new MockResourceServer({
checkForUpdates: [true],
getResources: [{}],
}),
new MockResourceRepository({
saveAll: [undefined],
}),
);
const result = await updateResources.execute();
expect(result).toBeTruthy();
});
What I like about these mocks is that all the responses are explicit, and show you the sequence of calls that are happening.
My Controller function definition looks like that:
async login(#Req() request, #Body() loginDto: LoginDto): Promise<any> {
How I could prepare/mockup Request to provide first argument of function from Jest test?
Inside funciton I am setting headers using request.res.set. Should I somehow pass real Request object to function and then check if header is set or rather mockup whole Request object and check if set function was called?
I managed to do that mocking requests and response using node-mocks-http library.
const req = mocks.createRequest()
req.res = mocks.createResponse()
and then passing this as an argument.
const data = await authController.login(req, loginDto)
expect(req.res.get('AccessToken')).toBe(token.accessToken)
I followed a different approach and instead of using node-mocks-http library I used #golevelup/ts-jest, also, instead of testing if the function returns some value, like res.json() or res.status(), I checked if the function was called with the value I wanted to.
I borrowed this approach from Kent C. Dodds's testing workshop, take a look for similar ideas. Anyway, this is what I did in order to mock the Response dependency of my Controller's route:
// cars.controller.spec.ts
import { createMock } from '#golevelup/ts-jest';
const mockResponseObject = () => {
return createMock<Response>({
json: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
});
};
... ommited for brevity
it('should return an array of Cars', async () => {
const response = mockResponseObject();
jest
.spyOn(carsService, 'findAll')
.mockImplementation(jest.fn().mockResolvedValueOnce(mockedCarsList));
await carsController.getCars(response);
expect(response.json).toHaveBeenCalledTimes(1);
expect(response.json).toHaveBeenCalledWith({ cars: mockedCarsList });
expect(response.status).toHaveBeenCalledTimes(1);
expect(response.status).toHaveBeenCalledWith(200);
});
And that's it, I think that the implementation details aren't that important but in any case I'll leave the link to the Github repo where you can find the whole project.
I am trying to learn how to test events emitted through a global Event Bus. Here's the code with some comments in the places I don't know what to do.
// EvtBus.js
import Vue from 'vue';
export const EvtBus = new Vue();
<!-- CouponCode.vue -->
<template>
<div>
<input
class="coupon-code"
type="text"
v-model="code"
#input="validate">
<p v-if="valid">
Coupon Redeemed: {{ message }}
</p>
</div>
</template>
<script>
import { EvtBus } from '../EvtBus.js';
export default {
data () {
return {
code: '',
valid: false,
coupons: [
{
code: '50OFF',
discount: 50,
message: '50% Off!'
},
{
code: 'FREE',
discount: 100,
message: 'Entirely Free!'
}
]
};
},
created () {
EvtBus.$on('coupon-applied', () => {
//console.info('had a coupon applied event on component');
});
},
methods: {
validate () {
// Extract the coupon codes into an array and check if that array
// includes the typed in coupon code.
this.valid = this.coupons.map(coupon => coupon.code).includes(this.code);
if (this.valid) {
this.$emit('applied');
// I NEVER see this on the coupon-code.spec.js
EvtBus.$emit('coupon-applied');
}
}
},
computed: {
message () {
return this.coupons.find(coupon => coupon.code === this.code).message;
}
}
}
</script>
// tests/coupon-code.spec.js
import expect from 'expect';
import { mount } from '#vue/test-utils';
import CouponCode from '../src/components/CouponCode.vue';
import { EvtBus } from '../src/EvtBus.js';
describe('Reminders', () => {
let wrp;
beforeEach(() => {
wrp = mount(CouponCode);
});
it('broadcasts the percentage discount when a valid coupon code is applied', () => {
let code = wrp.find('input.coupon-code');
code.element.value = '50OFF';
code.trigger('input');
console.log(wrp.emitted('applied'));
//
// I NEVER see this on the outpout.
// How can I test it through a global event bus rather than
// an event emitted from the component instance?
//
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
// Passes, but not using EvtBus instance.
expect(wrp.emitted('applied')).toBeTruthy;
});
});
So, my doubt is how to test that the global event bus is emitting and listening to events inside components that use that event bus.
So, is it possible to test the global Event Bus using Vue Test Utils or I should use another approach?
If component is using global EventBus, eg that's imported outside of given component and assigned to window.EventBus, then it's possible to use global Vue instance to redirect $on or $emit events to wrapper's vm instance. That way you can proceed writing tests as if component is emitting via this.$emit instead of EventBus.$emit:
it('clicking "Settings" button emits "openSettings"', () => {
global.EventBus = new Vue();
global.EventBus.$on('openSettings', (data) => {
wrapper.vm.$emit('openSettings', data);
});
// component emits `EventBus.$emit('openSettings')`
expect(wrapper.emitted('openSettings')).toBeTruthy(); // pass
});
Well,
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
This code in your spec file won't be called because the mounted wrp component is not using the same EvtBus you are importing in your spec file above.
What you require to test this is an npm package named inject-loader so that you can provide your own implementation(stub) of the EvtBus dependency of your coupon code component.
Somewhat like this
const couponCodeInjector = require('!!vue-loader?inject!src/views/CouponCode');
const stubbedModules = {
'../EvtBus.js': {
$on : sandbox.spy((evtName, cb) => cb());
}
};
const couponCode = couponCodeInjector(stubbedModules);
and then in your unit test you can assert whether the stubbedModules['../EvtBus.js'].$on has been called or not when code.trigger('input');
PS: I haven't used vue-test-utils. So I don't know exactly how to the stubbing with this npm package.
But the main thing you need to do is to find a way to stub your EvtBus dependency in the CouponCode component in such a way that you can apply a spy on it and check whether that spy has been called or not.
Unit tests should focus on testing a single component in isolation. In this case, you want to test if the event is emitted, since that is the job of CouponCode.vue. Remember, unit tests should focus on testing the smallest units of code, and only test one thing at a time. In this case, we care that the event is emitted -- EventBus.test.js is where we test what happens when the event is emitted.
Noe that toBeTruthy is a function - you need (). expect(wrp.emitted('applied')).toBeTruthy is actually not passing, since you need () - at the moment, it is actually doing nothing -- no assertion is made.
What your assertion should look like is:
expect(wrp.emitted('applied')).toBeTruthy()
You can go one step further, and ensure it was only emitted once by doing something like expect(wrp.emitted().applied.length).toBe(1).
You then test InputBus in isolation, too. If you can post the code for that component, we can work through how to test it.
I worked on a big Vue app recently and contributed a lot to the main repo and documentation, so I'm happy to help out wherever I can.
Let me know if that helps or you need more guidance. If possible, post EventBus.vue as well.
I got the same issue with vue-test-utils and Jest. For me, createLocalVue() of vue-test-utils library fixed the issue. This function creates a local copy of Vue to use when mounting the component. Installing plugins on this copy of Vue prevents polluting the original Vue copy. (https://vue-test-utils.vuejs.org/api/options.html#localvue)
Adding this to your test file will fix the issue:
const EventBus = new Vue();
const GlobalPlugins = {
install(v) {
// Event bus
v.prototype.$bus = EventBus;
},
};
// create a local instance of the global bus
const localVue = createLocalVue();
localVue.use(GlobalPlugins);
jest.mock('#/main', () => ({
$emit: jest.fn(),
}));
Include this in code in your spec file at the very begining.
Note: '#/main' is the file from which you are importing Event Bus.
I am using react/redux to generate a list of panels, each of which displays data on each list item. I set a 5 second interval that calls refreshAppList(this.props.list) action creator that forEach loops through every item in the list and makes an async call which then dispatches the refreshed list item (using redux-thunk). So basically, every 5 seconds I am refreshing the list of panels with the most up-to-date data. This works great! Unfortunately, now that I am writing unit tests for this particular async action creator I have run into an issue. .forEach does not return anything so when I call it in my unit tests I am getting undefined. Does anyone know how to override this issue or maybe i need to use a different method to refresh the entire list of panels?
Here is the action creator that is looping through the array and making an async call on each array item.
export const refreshAppList = list => (dispatch) => {
list.forEach((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
});
};
Here is the error i am receiving:
1) async actions creates an action with type: REFRESH_APP_LIST:
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (tests/asyncActions.js:140:12)
Here is where I am calling the action creator within the test (using redux-mock-store):
return store.dispatch(refreshAppList(list)).then(() => {
expect(store.getActions()).to.deep.equal(expectedActions);
});
I think it is also worth mentioning that I am using axios-mock-adapter to mock the data returned from the async call within the action creator.
One last thing: I have written unit tests for two other async action creators within the same app and both pass. The big difference is that this particular action creator is chaining together multiple async calls using a forEach loop (that is not returning anything to the test).
That doesn't work because the function that refreshAppList returns doesn't return anything. Also, .forEach doesn't return anything even though you do return axios.get. from inside. You could use .map instead and return everything inside Promise.all. Something like this
export const refreshAppList = list => (dispatch) => {
return Promise.all(list.map((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
}));
};
Im working on a test suite for an existing Backbone application using Jasmine and Sinon and I am testing that my router performs the correct actions on a certain route. Here's the actual route function:
favourites: function()
{
//Dont re-initialize the favourites view as there is no need.
//Instead, just render the favourite movies
if ( ! this.favMoviesView)
{
this.favMoviesView = new cinephile.Views.FavouriteMoviesView({
collection: cinephile.favouriteMovies
});
}
else
{
this.favMoviesView.renderFavourites();
}
$('#content').html(this.favMoviesView.el);
},
In my test suite I want to assert that when navigating to to the favourites route this.favMoviesView will be created once and then, if it exists will not re-initialize but instead just call this.favMoviesView.renderFavourites() which is a method that iterates over the view's collection.
Here's my test spec:
describe('cinephile.Routers.CinephileRouter', function () {
beforeEach(function () {
this.router = new cinephile.Routers.CinephileRouter();
this.routeSpy = sinon.spy();
try
{
Backbone.history.start({ silent : true });
}
catch(e) {}
this.router.navigate('elsewhere');
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView')
.returns(new Backbone.View());
});
afterEach(function () {
this.favouritesViewStub.restore();
});
describe('Favourites Route', function() {
it('should load the favourites on /favourites', function () {
this.router.bind('route:favourites', this.routeSpy);
this.router.navigate('favourites', true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith()).toBeTruthy();
});
it('creates a favourites view if one doesn\'t exist', function () {
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
});
it('Reuses the favourites view if one does exist and reset it\'s collection', function () {
this.router.favourites();
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
});
});
});
My first two tests pass and I believe them to correctly describe the favourites method in my router. The third test is the the one giving me problems. As I understand it, because I am testing my router and NOT the FavouriteMoviesView I should be stubbing out the view to keep the test isolated. If that is the correct assumption, my issue becomes that the stub won't have a renderFavourites method as it is a stubbed out Backbone.View().
How can I fix this particular problem and if you are so inclined, I believe I'm missing something conceptual so feel free to explain what it is that I'm not understanding.
Cheers.
You problem is that you want to mock something inside a mock function. What I would suggest is that instead of this...
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new Backbone.View());
...have this:
var StubView = Backbone.View.extend({
renderFavourites: sinon.stub()
});
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new StubView());
Now your View "constructor" will return a StubView, which has the method you are calling stubbed out. So this Backbone View with the stubbed out method will be placed in the router.favMoviesView -property. The favouritesViewStub property still contains just the "constructor" -function, so you can't access this stubbed method from there. This is why you haveto change this from the last test:
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
to something like this:
expect(this.router.favMoviesView.renderFavourites).toHaveBeenCalledTwice();
This way you will actually check if the router's copy of the view has had the method called twice.
Hope this works for you, comment if it doesn't! I didn't test this out, so there could be some problems, but I'm sure the logic behind works.