How to mock vue composable functions with jest - vuejs2

I'm using vue2 with composition Api, vuex and apollo client to request a graphql API and I have problems when mocking composable functions with jest
// store-service.ts
export function apolloQueryService(): {
// do some graphql stuff
return { result, loading, error };
}
// store-module.ts
import { apolloQueryService } from 'store-service'
export StoreModule {
state: ()=> ({
result: {}
}),
actions: {
fetchData({commit}) {
const { result, loading, error } = apolloQueryService()
commit('setState', result);
}
},
mutations: {
setState(state, result): {
state.result = result
}
}
}
The Test:
// store-module.spec.ts
import { StoreModule } from store-module.ts
const store = StoreModule
describe('store-module.ts', () => {
beforeEach(() => {
jest.mock('store-service', () => ({
apolloQueryService: jest.fn().mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
})
}))
})
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}
The test fails, because the commit gets called with ('setData', { value: undefined }) which is the result from the original apolloQueryService. My Mock doesn't seem to work. Am I doing something wrong? Appreciate any help, thanks!

Try this :
// store-module.spec.ts
import { StoreModule } from store-module.ts
// first mock the module. use the absolute path to store-service.ts from the project root
jest.mock('store-service');
// then you import the mocked module.
import { apolloQueryService } from 'store-service';
// finally, you add the mock return values for the mock module
apolloQueryService.mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
});
/* if the import order above creates a problem for you,
you can extract the first step (jest.mock) to an external setup file.
You should do this if you are supposed to mock it in all tests anyway.
https://jestjs.io/docs/configuration#setupfiles-array */
const store = StoreModule
describe('store-module.ts', () => {
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}

Related

How to Implement nuxtServerInit Action to load data from server-side on the initial load in Pinia (Nuxt3)

My Code:
export const useMenuStore = defineStore("menuStore", {
state: () => ({
menus: [],
}),
actions: {
async nuxtServerInit() {
const { body } = await fetch("https://jsonplaceholder.typicode.com/posts/1").then((response) => response.json());
console.log(body);
this.menus = body;
resolve();
},
},
});
NuxtServerInit is not working on initial page render on nuxt js vuex module mode.Anyone know this error please help me.
NuxtServerInit is not implemented in Pinia, but exists a workaround.
Using Pinia alongside Vuex
// nuxt.config.js
export default {
buildModules: [
'#nuxtjs/composition-api/module',
['#pinia/nuxt', { disableVuex: false }],
],
// ... other options
}
then Include an index.js file inside /stores with a nuxtServerInit action which will be called from the server-side on the initial load.
// store/index.js
import { useSessionStore } from '~/stores/session'
export const actions = {
async nuxtServerInit ({ dispatch }, { req, redirect, $pinia }) {
if (!req.url.includes('/auth/')) {
const store = useSessionStore($pinia)
try {
await store.me() // load user information from the server-side before rendering on client-side
} catch (e) {
redirect('/auth/login') // redirects to login if user is not logged in
}
}
}
}
In Nuxt2, the Nuxt will run the code in nuxtServerInit() of store/index.js on the server-side to boot the app.
However, in Nuxt3, there is no specific place to write the boot code, you can write the boot code anywhere instead of in nuxtServerInit() of store/index.js.
It might be helpful, especially when you need to send a request before boosting the app.
your pinia file may define like following:
store/menu.js
import { defineStore } from 'pinia';
export const useMenuStore = defineStore('menuStore', {
state: () => ({
_menus: [],
}),
getters: {
menus() {
return this._menus;
}
},
actions: {
async boot() {
const { data } = await useFetch('https://jsonplaceholder.typicode.com/posts/1');
this._menus = data;
}
}
});
Then, create a plugin which named as *.server.[ts|js], for example init.server.js
(.sever.js tail will let the file only run in server side)
plugins/init.server.js
import { defineNuxtPlugin } from '#app';
import { useMenuStore } from '~/store/menu.js';
export default defineNuxtPlugin(async (nuxtApp) => {
const menu = useMenuStore(nuxtApp.$pinia);
await menu.boot();
});
nuxt.config.js
modules: [
'#pinia/nuxt',
],
There is an entire example of SSR Nuxt3 with authorization that may help

mocking setInterval in created hook (vue.js)

I am trying to mock a setInterval inside my created hook but no matter what I try
the function is never called. What I have done so far is using jest.useFakeTimers and inside
each test I would use jest.advanceTimersByTime(8000) to check if my api is being called.
I would appreciate any opinions/help. thanks
my vue file
created() {
setInterval(() => this.checkStatus(), 8000)
},
methods: {
async checkStatus() {
let activated = false
if (!this.isLoading) {
this.isLoading = true
let res = await this.$UserApi.getUserActivateStatus(this.accountId)
this.isLoading = false
if (res.success) {
activated = res.activated
}
if (activated) {
console.log("activated")
} else {
console.log("error")
}
}
}
}
my test file
import { shallowMount, config } from "#vue/test-utils"
import Step4 from "../../../login/smart_station/step4"
describe("Step4", () => {
let wrapper
const $route = {
query: {
account_id: "99"
}
}
const mockGetUserActivateStatus = jest.fn(() =>
Promise.resolve({ success: true, activated: true })
)
beforeEach(() => {
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
jest.useFakeTimers()
})
it("activates status every 8secs", async () => {
jest.advanceTimersByTime(9000)
expect(mockGetUserActivateStatus).toHaveBeenCalled()
})
})
Jest's Timer Mocks replace the native timer functions like setInterval with their own versions that can be controlled.
Your problem is that you are telling Jest to replace these functions after your component is created and mounted. Since you're using setInterval within your component's created hook, this will still be using the real version.
Move the jest.useFakeTimers() to the top of the beforeEach setup function
beforeEach(() => {
jest.useFakeTimers()
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
})

Mock Native Module Jest

In my React-Native application i wanna write some unit tests for my Native Libraries.
dataStorage.js
import RNDataStorage, {ACCESSIBLE} from "react-native-data-storage";
const dataStorage = {
setData: function (key, value) {
return RNDataStorage.set(key, value, {accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY})
.then(res => {
console.log(res);
return true;
})
},
}
export default dataStorage;
dataStorage.test.js
import dataStorage from '../../src/services/dataStorage'
jest.mock('react-native-data-storage', () => {
return {
RNDataStorage: {
set: jest.fn(),
}
};
});
it('Should return Access & RefreshToken', function () {
dataStorage.setData('John', 'Test');
});
When i run this setup i receive the error: TypeError: Cannot read property 'set' of undefined.
What is the correct way to mocks some modules? Thanks for any help
The module you are mocking is an ES6 module with a default export and a named export.
Mocking it like this should get your test running:
jest.mock('react-native-data-storage', () => {
return {
__esModule: true,
default: {
set: jest.fn(() => Promise.resolve('the response'))
},
ACCESSIBLE: {
ALWAYS_THIS_DEVICE_ONLY: true
}
};
});
Answer based on this post

Testing NgRx 6 Effects

I am trying to test a ngrx effects in Angular 6 project, I always get error:
Expected $[0].notification.kind = 'C' to equal 'N'.
Expected $[0].notification.hasValue = false to equal true.
I tried this post https://brianflove.com/2018-06-28/ngrx-testing-effects and the one in the ngrx doc. Is there any requirements to make test on effects with ngrx 6 ? The error is not meaningful enough for me. Maybe someone have a complete example about how to do ?
Here's my effect:
initData$: Observable<Action> = this.actions$.pipe(
ofType(INIT_DATA_ACTION),
switchMap((data: any) => {
return this.store.pipe(select(getAppDataResolved)).take(1).switchMap((resolved: any) => {
if (!resolved) {
return this.dataService.getInitData(this.loginService.user.id).switchMap((response: any) => {
return Observable.from([
new ItemsInitDataAction(response.userItems),
new InitDataResolvedAction(),
]);
});
} else {
return Observable.from([
new InitDataResolvedAction(),
]);
}
});
}),
);
and my karma test:
it('should be created', () => {
expect(effects).toBeTruthy(); // got success
});
it('basic test', () => { // got error
const action = new appAction.InitDataAction();
const outcome = new appAction.InitDataResolvedAction();
actions.stream = hot('a', { a: action });
const expected = hot('a', { b: outcome });
expect(effects.initData$).toBeObservable(expected);
});
});
Thanks in advance for helping ;-)
I think you need to insert a mock for the Selector, the next line in the console should be describe something about missing selector data.
let store: Store<any>
class MockStore {
select(){}
}
TestBed.configureTestingModule({
providers: [
{
provide: Store,
useClass: MockStore
}
]
});
store = TestBed.get(Store);
And in test suite you can use Spy to give you any slice of store that you want:
spyOn(store, 'select').and.returnValue(of(initialState));
There is a Typo in expected. It should be 'b' instead of 'a'
const expected = hot('b', { b: outcome });
I havent been able to get a testing working with marbles yet.
Im using Nrwl nx, so my effects test looks like this:
import { TestBed } from '#angular/core/testing';
import { Subject, ReplaySubject } from 'rxjs';
import { EffectsModule } from '#ngrx/effects';
import { StoreModule } from '#ngrx/store';
import { provideMockActions } from '#ngrx/effects/testing';
import { NxModule } from '#nrwl/nx';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordEffects } from './change-password.effects';
import { ChangePassword, ChangePasswordSuccessful } from './change-password.actions';
import { HttpClientTestingModule } from '#angular/common/http/testing';
describe('ChangePasswordEffects', () => {
let actions: Subject<any>;
let effects: ChangePasswordEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NxModule.forRoot(), StoreModule.forRoot({}), EffectsModule.forRoot([]), HttpClientTestingModule],
providers: [ChangePasswordEffects, DataPersistence, provideMockActions(() => actions)]
});
effects = TestBed.get(ChangePasswordEffects);
});
describe('loadChangePassword$', () => {
it('should work', () => {
actions = new ReplaySubject(1);
actions.next(ChangePassword);
effects.loadChangePassword$.subscribe(result => {
expect(result).toEqual(ChangePasswordSuccessful);
});
});
});
});
And my code looks like this:
import { PasswordChangeError } from './../../models/password-change-error';
import { Injectable } from '#angular/core';
import { Effect, Actions } from '#ngrx/effects';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordPartialState } from './change-password.reducer';
import {
ChangePassword,
ChangePasswordSuccessful,
ChangePasswordError,
ChangePasswordActionTypes
} from './change-password.actions';
import { ChangePasswordService } from '../../services/change-password/change-password.service';
import { map } from 'rxjs/operators';
#Injectable()
export class ChangePasswordEffects {
#Effect() loadChangePassword$ = this.dataPersistence.fetch(ChangePasswordActionTypes.ChangePassword, {
run: (action: ChangePassword, state: ChangePasswordPartialState) => {
return this.passwordService
.changePassword(action.newPassword, action.userId)
.pipe(map(res => new ChangePasswordSuccessful(res)));
},
onError: (action: ChangePassword, error: PasswordChangeError) => {
return new ChangePasswordError(error);
}
});
constructor(
private actions$: Actions,
private dataPersistence: DataPersistence<ChangePasswordPartialState>,
private passwordService: ChangePasswordService
) {}
}

How to correctly test effects in ngrx 4?

There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}