I'm trying to test my app's redux actions using Jest. I've looked at the testing site # redux.js.org and other forums posts but I can't see why I'm getting the error I am, I think I'm not getting how the mocking works.
My action is in index.js:
export const setResponse = (res) => {
return {
type: 'RESPONSE_RECEIVED',
payload: res
}
};
My test is actions.test.js:
import * as actions from '../src/client/scripts/actions/index'
describe('actions', () => {
it('should create an action to receive a response', () => {
const payload = {response: 'solr_json'};
const expectedAction = {
type: 'RESPONSE_RECEIVED',
payload
}
expect(actions.setResponse(payload)).toEqual(expectedAction)
})
});
I got the error 'Expected undefined to equal Object...' soI thought actions.setResponse didn't exist, so I printed out actions.setResponse and it looks like this:
{ [Function: setResponse]
_isMockFunction: true,
getMockImplementation: [Function],
mock: { calls: [], instances: [] },
mockClear: [Function],
mockReturnValueOnce: [Function],
mockReturnValue: [Function],
mockImplementationOnce: [Function],
mockImpl: [Function],
mockImplementation: [Function],
mockReturnThis: [Function] }
So it seems the function is there, how do I actually call the function inside the Jest test? Even if I change
export const setResponse = (res) =>
to
export function setResponse(res)
(so it's the same format of the redux.js.org tutorial) I get the same error. For reference this is the jest part of my package.json:
"jest": {
"scriptPreprocessor": "node_modules/babel-jest",
"testFileExtensions": [
".test.js"
],
"testPathDirs": [
"__tests__"
],
"moduleFileExtensions": [
"js"
],
"unmockedModulePathPatterns": [
"node_modules/react",
"node_modules/react-addons-test-utils",
"node_modules/react-dom"
]
}
Thanks for any help
The problem seems that the module you want to test is mocked, cause you use unmockedModulePathPatterns. From the docs:
An array of regexp pattern strings that are matched against all
modules before the module loader will automatically return a mock for
them. If a module's path matches any of the patterns in this list, it
will not be automatically mocked by the module loader.
I would guess that if you remove this line the module is not automatically mocked. Normally its easier to mock the stuff that you want to mocked directly in your test using jest.mock. Or you add your source folder to the list of unmockedModulePathPatterns.
Related
Our test runner is Jest.
Our component is marked as standalone: true,
If try to set up spectator like this:
describe('OurComponent', () => {
let spectator: Spectator<OurComponent>;
const fakeActivatedRoute: ActivatedRoute = {
snapshot: {data: {}},
} as ActivatedRoute;
const componentFactory: SpectatorFactory<OurComponent> = createComponentFactory({
component: OurComponent,
imports: [
// some imports
],
providers: [
// some providers
],
detectChanges: false,
shallow: true,
});
beforeEach(async () => {
spectator = componentFactory();
});
it('should be created', () => {
expect(spectator).toBeDefined();
});
});
Then we run into the following error:
"Error: Unexpected "OurComponent" found in the "declarations" array of the "TestBed.configureTestingModule" call, "OurComponent" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?"
Using the Angular-CLI in order to generate resulted in a component with a test file which is built upon ComponentFixture.
How can we make it possible to test a standalone component using Spectator?
Depends on your spectator version (mine is 10.0.0) but you can use the declareComponent property :
const componentFactory: SpectatorFactory<OurComponent> = createComponentFactory({
component: OurComponent,
declareComponent: false,
});
I have broken up my Vuex store into namespaced modules.
I have created a User module, where I need to call my Showrooms module. The interesting thing, is that this action works fine. But I am updating the store:
dispatch('showrooms/updateLocalShowroom',
{
weddingId: element.wedding.id,
showroomDresses: element.showroom.showroomDresses,
status: element.showroom.status,
uuid: element.showroom.uuid,
},
{
root: true
}
)
But when I try and reset the Showrooms store with this:
dispatch('showrooms/resetShowrooms', { root: true })
I get the following error:
[vuex] unknown local action type: showrooms/resetShowrooms, global type: user/showrooms/resetShowrooms
The only thing I can think of is something is that when I resetShowrooms I do it like this in my store module:
This action:
resetShowrooms: ({ commit }) => {
commit('zeroOutShowrooms')
},
Calls this mutation
zeroOutShowrooms: (state) => {
Vue.set(state, 'showrooms', [])
}
It ends up that dispatch is looking for some data in the second position. That's why this was NOT working:
resetShowrooms: ({ commit }) => {
commit('zeroOutShowrooms')
},
While this DID work and solved my issue:
dispatch('showrooms/resetShowrooms', '', { root: true })
Just sending the empty string did the trick and all is good now.
After I gone through the below video for ngrx isolated testing:
John Crowson - Using MockStore in NgRx 8 | AngularUP
I tried to implement the same with my simple project. But I am getting error which I am not able to understand. any one help me to get solved?
it's very big help for me.
test ts file:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { provideMockStore, MockStore } from '#ngrx/store/testing';
import { ShellHomeComponent } from './shell-home.component';
import { StoreOne } from './../../models';
import { Store, select } from '#ngrx/store';
import { cold } from 'jasmine-marbles';
describe('ShellHomeComponent', () => {
let component: ShellHomeComponent;
let fixture: ComponentFixture<ShellHomeComponent>;
let mockStore: MockStore<StoreOne>;
const loadingState = {
loading: true,
items: [{ name: '1' }]
} as StoreOne;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShellHomeComponent ],
imports: [],
providers: [provideMockStore({initialState: loadingState})]
})
.compileComponents();
mockStore = TestBed.get(Store);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShellHomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display loading as true', () => {
const expected = cold('loading', { loading: false, items: [{ name: '3' }] });
expect(component.loading).toBeObservable(expected);
});
});
after run I am getting the following error:
ShellHomeComponent › should display loading as true
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Array [
Object {
"frame": 0,
"notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": true,
- },
- },
- Object {
- "frame": 10,
- "notification": Notification {
- "error": undefined,
+ "error": [TypeError: Cannot read property 'loading' of undefined],
"hasValue": false,
- "kind": "C",
+ "kind": "E",
"value": undefined,
},
},
]
41 | it('should display loading as true', () => {
42 | const expected = cold('a|', { a: true });
> 43 | expect(component.loading).toBeObservable(expected);
| ^
44 | });
45 |
46 | });
at compare (node_modules/jasmine-marbles/bundles/jasmine-marbles.umd.js:379:33)
at src/app/module1/shell/shell-home/shell-home.component.spec.ts:43:35
console.warn node_modules/#ngrx/store/bundles/store.umd.js:608
The feature name "storeOne" does not exist in the state, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('storeOne', ...) or StoreModule.forFeature('storeOne', ...). If the default state is intended to be undefined, as is the case with router state, this development-only warning message can be ignored.
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.321s
I had a similar problem. My tests were failing because state in my reducer was undefined. I was also getting a warning in the console that The feature name "my-feature" does not exist in the state's root, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('my-feature', ...) or StoreModule.forFeature('my-feature', ...).
The problem was that I was providing the mock store for the feature when I needed to provide the mock store for the entire app.
Try changing provideMockStore({initialState: loadingState}) to something like provideMockStore<State>({initialState: {shellComponent: loadingState}}) where State is the name of your application's global state (make sure that you import State from your application's state.ts file, not #ngrx/store), and shellComponent is the name of the feature you're testing.
To build off Danny's answer, you will do this:
providers: [
provideMockStore({
initialState: {
'addInvestigationModal': initialState
}
})
]
However, I still had an error An error was thrown in afterAll
error properties: Object({ longStack: 'TypeError: Cannot read property 'property_name' of undefined.
I fixed this by adding
afterEach(() => {
fixture.destroy();
});
I was getting this same warning as #Danny:
The feature name "some-feature" does not exist in the state's root, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('some-feature', ...) or StoreModule.forFeature('some-feature', ...).
I forget that I had to add the module into the import list.
const someFeatureModule = StoreModule.forFeature('some-feature', someFeatureReducer);
...
#NgModule({
imports: [
someFeatureModule <-- this was missing
]
...
I'm working on a survey builder in Vue, and survey questions which the user creates are committed to Vuex so they can be retrieved later like so:
computed: {
inputs() {
return this.$store.getters.questions(this.pageNumber);
},
},
pageNumber is a prop the component receives and inputs() returns an array of questions. This all seems to work in terms of rendering the correct questions on screen but I'm having trouble with Jest tests.
In order to test I was hoping I could mock the store with getters like my attempt below (omitting some parts):
const localVue = createLocalVue();
localVue.use(Vuex);
beforeEach(() => {
state = {
survey: {
pages: [
// pages objects
],
},
};
getters = {
questions: () => [
{ type: 'Radio', config: { label: 'Test label', options: [{ label: 'Test option label' }] }, validation: [] },
],
};
store = new Vuex.Store({
state,
getters,
});
});
But this results in the error:
TypeError: this.$store.getters.questions is not a function
However, removing that arrow function from getters.questions gives me:
[vuex] getters should be function but "getters.questions" is [{"type":"Radio","config":{"label":"Test label","options":[{"label":"Test option label"}]},"validation":[]}].
So I think I could be completely misunderstanding. Could someone point me in the right direction?
The getters of a store are just like computed properties on components, they are defined using functions but accessed as properties, without the parentheses.
Given this line:
return this.$store.getters.questions(this.pageNumber);
it would appear that your questions getter is returning a function that accepts a pageNumber. That isn't what you're currently defining in your test getter, you're just returning an array.
So either the invocation needs to change to use square brackets:
return this.$store.getters.questions[this.pageNumber];
or the getter needs to return a function:
getters = {
questions: () => () => [
{ type: 'Radio', config: { label: 'Test label', options: [{ label: 'Test option label' }] }, validation: [] }
]
};
If it helps to clarify, this is equivalent to:
getters = {
questions: function () {
return function () {
const questions = [
{ type: 'Radio', config: { label: 'Test label', options: [{ label: 'Test option label' }] }, validation: [] }
];
return questions;
};
}
};
Note that I'm completely ignoring the passed pageNumber as I assume your test getter is hard-coded to return the correct array of questions.
You may wish to consult with the non-test version of this getter as I expect you'll see it returns an extra level of function.
Under vue-test-utils and Jest you could easily mock a getter (especially deep nested module getter) if you replace your mocked store with the following:
Approach #1
store: new Vuex.Store({
getters: {
'top-level-module/nested-module-level-1/more-nested-module/dataItem': function () {
return 'any fake data you want'
}
}
}),
This soulution meakes you free from writing deep nested store objects mocks if you just want to test a component on a mocked getter (say you obtain value from the nested store module in your component's computed). Just return the fake (!) object of your preference from the mock getter.
Note you do not need to mock store state if you do not test it.
As well do not test the getter's returned object in this test. Extract it to the separate dedicated test.
Approach #2
You would not even need Vuex. Just use vue-test-utils mocks as follows:
mocks: {
$store: {
getters: {
'top-level-module/nested-module-level-1/more-nested-module/dataItem': (function () {
return 'any fake data you want'
})()
}
}
},
Note that vs Approach #1 you have to make your mock getter to be an IIFE to get the value from mock as the mock does not invoke getter as Vuex does.
Write the questions method in getters like this:
getters = {
questions: (state) => (page_number)=>{ [
{ type: 'Radio', config: { label: 'Test
label',
options: [{ label: 'Test option label' }]
},
validation: [] },
],
}
};
In my case, I created a new module but I forgot to register it in the index.js file.
New file in src/store/modules: entities.js
Then in the index.js file:
import entities from "./modules/entities";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
...
entities,
},
});
I am trying to run my unit tests with Karma. I have calendar.tests.js file that looks a lot like this:
import { handleSelectDay } from '../components/Calendar/index.vue'
describe('Calendar', () => {
describe('handleSelectDay', () => {
const data = {};
describe('updates data', () => {
it('should set the selectedDay to a new id', () => {
handleSelectDay('id');
expect(data.daySelected).to.equal('id');
});
});
});
});
When I run this test with Karma I get the following: TypeError: (0 , _index.handleSelectDay) is not a function is not a function
My karma.conf.js looks like:
module.exports = function (config) {
config.set({
frameworks: ['browserify', 'mocha'],
files: ['static/js/apps/FutureHours/test/calender.tests.js'],
preprocessors: {
'static/js/apps/**/test/*.js': ['browserify']
},
browsers: ['Chrome'],
logLevel: 'LOG_DEBUG',
plugins: [
'karma-mocha',
'karma-browserify',
'karma-chrome-launcher',
'karma-spec-reporter'
],
browserify: {
debug: true,
transform: [['babelify', { presets: ['env'] }], 'vueify']
}
})
}
How can I get Karma to play nice with VueJS single file components?
The problem with this is you can't have a named export from a .vue component. Instead, any methods used in the component will have to accessed via the component object in a unit test. Any functions used outside the component's methods, should probably live in their own ES module to make unit testing them much easier.