I'm build a store, and want to make action generator or others with a plugin.
On there met a problem. Because of this.
That is impossible thing on Nuxt & Vuex?
I posted plugin's source code ( with some black box )
My final goal is generateActionList with configuration object
export default ({ app }, inject) => {
const callAxios = (/* params */) => { /* some codes */ }
const generateAction = (config) => {
return (context, payload) => callAxios(/* params */)
}
const generateActionList = (config) => { // <= it's my Goal
const actions = {}
for (const [key, config] of Object.entries(conf)) {
actions[key] = generateAction(config)
}
return actions
}
inject('storeUtil', {
callAxios,
generateAction,
generateActionList
})
}
if have some solution, please talk to me.
i saw, 'this' is only lexically available on document.
ps) https://nuxtjs.org/guide/vuex-store/
Store is a module, not an instance of a class, so no this.
Instead of ...this.$storeUtil, get it from Vue directly.
Vue.prototype.$storeUtil
It might also be available at Vue.$storeUtil. If not, you could always init it there in storeUtil.
As an reference, this is how filters are reused outside of a component.
If it was never registered as prototype, you can just import it directly.
import storeUtil from '../storeUtil'
//...
[...storeUtil]
///...
Related
I have defined 2 JEST mocks. The problem I am getting. The first mock doesn't work but the second does.
import Helper from '../../test-helper'
import Storage from '#/storage'
import GuestContext from '#/auth/guest-context'
import UserContext from '#/auth/user-context'
// import LocalStorageGateway from '#/storage/local/local-storage-gateway'
const mockContextUpsert = jest.fn()
jest.mock('#/storage/local/local-storage-gateway', () => {
return jest.fn().mockImplementation((authContext) => {
return {
authContext,
context: {
upsert: mockContextUpsert
}
}
})
})
// import RemoteStorageGateway from '#/storage/remote/remote-storage-gateway'
const mockFetch = jest.fn()
jest.mock('#/storage/remote/remote-storage-gateway', () => {
return jest.fn().mockImplementation((authContext) => {
return {
authContext,
fetch: mockFetch
}
})
})
I have tried...
Commenting out the second mock definition
Switching the orders
Using mockFetch in the first mock instead of mockContentUpsert (after of course moving the definition of mockFetch up).
Making the first mock a complete replica of the second with only the '#/storage/local/local-storage-gateway' line being changed.
The error I am getting is...
ReferenceError: mockContextUpsert is not defined
I cannot understand why the first mock doesn't work when the second mock works perfectly.
This will also work, adding the mock variables to the second declaration (not of any use as they're different classes but for reference)...
const mockContextUpsert = jest.fn()
const mockFetch = jest.fn()
jest.mock('#/storage/remote/remote-storage-gateway', () => {
return jest.fn().mockImplementation((authContext) => {
return {
authContext: authContext,
fetch: mockFetch,
context: {
upsert: mockContextUpsert
}
}
})
})
The classes being mocked here are almost identical.
UPDATE
Removing the reference to GuestContext() [this is additional complication, has since been removed, and is confusing the actual problem the question is trying to ask]
I have solved this eventually - The issue here wasn't with the mocks themselves but the class being mocked.
The LocalStorageGateway class is used to create a private instance within an imported module Storage, as follows...
const guestLocal = new LocalStorageGateway(new GuestContext())
This static context is causing the mocked constructor to execute before the variables are defined as Storage is one of the first module imported.
There are several ways around this...
You can change this...
import Helper from '../../test-helper'
import Storage from '#/storage'
import GuestContext from '#/auth/guest-context'
import UserContext from '#/auth/user-context'
(insert mocks here)
to...
const mockContextUpsert = jest.fn()
import Helper from '../../test-helper'
import Storage from '#/storage'
import GuestContext from '#/auth/guest-context'
import UserContext from '#/auth/user-context'
for example (yuk?).
Alternatively you can wrap mockContextUpsert up in a wrapping function (my earlier not-really-good-enough answer) -> This feels a bit tidier to me.
const mockContextUpsert = jest.fn()
jest.mock('#/storage/local/local-storage-gateway', () => {
return jest.fn().mockImplementation((authContext) => {
return {
authContext: authContext,
context: {
upsert: (cxt) => {
mockContextUpsert(cxt)
}
}
}
})
})
I could also make guestLocal a function but I don't really want to do that and create new gateway instances every time I want to use it (It's why it's there in the first place). If Storage was a class it would be instantiated in the constructor but it isn't and has no real need to be.
Thanks to #Teneff for his input on this, his answer got my brain looking at it the right way around. the variables are not hoisted was key - They work differently - I was operating on an incorrect understanding that anything called mockXXXX would be hoisted up above the mock calls but they aren't they are just 'allowed'.
A quote from the jest documentation:
Jest will automatically hoist jest.mock calls to the top of the module (before any imports)
meaning that whenever you try to define the function being returned by the mock mockGuestContext is not yet defined
What you can do is create an auto-mock
import localStorageGateway from '#/storage/local/local-storage-gateway';
jest.mock('#/storage/local/local-storage-gateway');
// and since localStorageGateway is a function
// jest will automatically create jest.fn() for it
// so you will be able to create authContext
const authContext = new GuestContext();
// and use it within the return value
localStorageGateway.mockReturnValue({
authContext,
context: {
upsert: jest.fn(),
}
})
Check this out:
import accountModule from '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
export default new Vuex.Store({
modules: {
account: accountModule,
other: otherModule,
}
});
The data initialization in other depends on the account module because the account module has user specific settings. Suppose other.state.list depends on account.state.settings.listOrder. However, I want the data for the account module to come from the server. Which is async. So when other is trying to get set up, it can't just try to reference account.state.settings.listOrder because the response from the server may not have come back yet.
I tried exporting a promise in accountModule that resolves with the module itself. But that approach doesn't seem to work.
import accountModulePromise from '#/store/modules/account/account';
accountModulePromise.then(function (accountMoudle) {
import otherModule from '#/store/modules/other/other';
...
});
This gives me an error saying that import statements need to be top level.
The following doesn't work either:
let accountModule = await import '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
...
It gives me an error saying that await is a reserved word. I'm confused though, because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import says that I should be able to do it.
Your last code block didn't work because of await have to be inside async function.
Remember, the await keyword is only valid inside async functions. If
you use it outside of an async function's body, you will get a
SyntaxError.
From MDN.
You can use Dynamic Module Registration:
accountModulePromise.then(async () => {
let otherModule = await import('#/store/modules/other/other');
store.registerModule('other', otherModule.default);
});
But when you want to get state or dispatch actions you have to check whether module is registered which is pretty bad.
In my opinion it would be better if you redesign your module structure to decoupling each other. Try to move your initialize code to main.js or App.vue then dispatch actions to update module states from that.
Updates
From your last update, Another idea to decoupling your store, I think you should store your list without order and sort it only when you use. You can do this with:
Computed property:
...
computed: {
list () {
let list = this.$store.state.other.list
let order = this.$store.state.account.settings.listOrder
if (!list || !order) return []
return someSort(list, order)
}
},
beforeCreate () {
this.$store.dispatch('other/fetchList')
this.$store.dispatch('account/fetchListOrder')
}
...
Or Vuex getters:
...
getters: {
list: (state) => (order) => {
return someSort(state.list, order)
}
}
...
...
computed: {
list () {
let order = this.$store.state.account.settings.listOrder
return this.$store.getters['others/list'](order)
}
}
...
Okay, so you have two modules. One with state that is fetched from the server, the other with state that is dependent on the first, correct?
I would suggest the following approach:
Set up your modules with empty 'state' to begin with. Then create an action within accountModule to set up the state from the server. Use a getter on other to order the list. Finally, dispatch your action upon app creation.
const account = {
namespaced: true,
state: {
listOrder: ''
},
mutations: {
setListOrder (state, newListOrder) {
state.listOrder = newListOrder
}
},
actions: {
async fetchServerState (ctx) {
let result = await fetch("/path/to/server")
ctx.commit('setListOrder', result.listOrder)
// or whatever your response is, this is an example
}
}
}
const other = {
namespaced: true,
state: {
unorderedList: []
},
getters: {
list (state, getters, rootState) {
return someSort(state.unorderedList, rootState.account.listOrder);
}
}
}
within App.vue (or wherever)
created () {
this.$store.dispatch('account/fetchServerState')
}
I'm having a pretty complex selectors structure in my project (some selectors may have up to 5 levels of nesting) so some of them are very hard to test with passing input state and I would like to mock input selectors instead. However I found that this is not really possible.
Here is the most simple example:
// selectors1.js
export const baseSelector = createSelector(...);
-
// selectors2.js
export const targetSelector = createSelector([selectors1.baseSelector], () => {...});
What I would like to have in my test suite:
beforeEach(() => {
jest.spyOn(selectors1, 'baseSelector').mockReturnValue('some value');
});
test('My test', () => {
expect(selectors2.targetSelector()).toEqual('some value');
});
But, this approach won't work as targetSelector is getting reference to selectors1.baseSelector during initialization of selectors2.js and mock is assigned to selectors1.baseSelector after it.
There are 2 working solutions I see now:
Mock entire selectors1.js module with jest.mock, however, it won't work if I'll need to change selectors1.baseSelector output for some specific cases
Wrap every dependency selectors like this:
export const targetSelector = createSelector([(state) => selectors1.baseSelector(state)], () => {...});
But I don't like this approach a lot for obvious reasons.
So, the question is next: is there any chance to mock Reselect selectors properly for unit testing?
The thing is that Reselect is based on the composition concept. So you create one selector from many others. What really you need to test is not the whole selector, but the last function which do the job. If not, the tests will duplicate each other, as if you have tests for selector1, and selector1 is used in selector2, then automatically you test both of them in selector2 tests.
In order to achieve:
less mocks
no need to specially mock result of composed selectors
no test duplication
test only the result function of the selector. It is accessible by selector.resultFunc.
So for example:
const selector2 = createSelector(selector1, (data) => ...);
// tests
const actual = selector2.resultFunc([returnOfSelector1Mock]);
const expected = [what we expect];
expect(actual).toEqual(expected)
Summary
Instead of testing the whole composition, and duplicating the same assertion, or mocking specific selectors outputs, we test the function which defines our selector, so the last argument in createSelector, accessible by resultFunc key.
You could achieve this by mocking the entire selectors1.js module, but also importing is inside test, in order to have access over the mocked functionality
Assuming your selectors1.js looks like
import { createSelector } from 'reselect';
// selector
const getFoo = state => state.foo;
// reselect function
export const baseSelector = createSelector(
[getFoo],
foo => foo
);
and selectors2.js looks like
import { createSelector } from 'reselect';
import selectors1 from './selectors1';
export const targetSelector = createSelector(
[selectors1.baseSelector],
foo => {
return foo.a;
}
);
Then you could write some test like
import { baseSelector } from './selectors1';
import { targetSelector } from './selectors2';
// This mocking call will be hoisted to the top (before import)
jest.mock('./selectors1', () => ({
baseSelector: jest.fn()
}));
describe('selectors', () => {
test('foo.a = 1', () => {
const state = {
foo: {
a: 1
}
};
baseSelector.mockReturnValue({ a: 1 });
expect(targetSelector(state)).toBe(1);
});
test('foo.a = 2', () => {
const state = {
foo: {
a: 1
}
};
baseSelector.mockReturnValue({ a: 2 });
expect(targetSelector(state)).toBe(2);
});
});
jest.mock function call will be hoisted to the top of the module to mock the selectors1.js module
When you import/require selectors1.js, you will get the mocked version of baseSelector which you can control its behaviour before running the test
For anyone trying to solve this using Typescript, this post is what finally worked for me:
https://dev.to/terabaud/testing-with-jest-and-typescript-the-tricky-parts-1gnc
My problem was that I was testing a module that called several different selectors in the process of creating a request, and the redux-mock-store state that I created wasn't visible to the selectors when the tests executed. I ended up skipping the mock store altogether, and instead I mocked the return data for the specific selectors that were called.
The process is this:
Import the selectors and register them as jest functions:
import { inputsSelector, outputsSelector } from "../store/selectors";
import { mockInputsData, mockOutputsData } from "../utils/test-data";
jest.mock("../store/selectors", () => ({
inputsSelector: jest.fn(),
outputsSelector: jest.fn(),
}));
Then use .mockImplementation along with he mocked helper from ts-jest\utils, which lets you wrap each selector and give custom return data to each one.
beforeEach(() => {
mocked(inputsSelector).mockImplementation(() => {
return mockInputsData;
});
mocked(outputsSelector).mockImplementation(() => {
return mockOutputsData;
});
});
If you need to override the default return value for a selector in a specific test, you can do that inside the test() definition like this:
test("returns empty list when output data is missing", () => {
mocked(outputsSelector).mockClear();
mocked(outputsSelector).mockImplementationOnce(() => {
return [];
});
// ... rest of your test code follows ...
});
I ran into the same problem. I ended up mocking the reselect createSelector with jest.mock to ignore all but the last argument (which is the core function you want to test) when it comes to testing. Overall this approach has served me well.
Problem I had
I had a circular dependency within my selector modules. Our code base is too large and we don't have the bandwidth to refactor them accordingly.
Why I used this approach?
Our code base has a lot of circular dependencies in the Selectors. And trying to rewrite and refactor them to not have circular dependencies are too much work. Therefore I chose to mock createSelector so that I don't have to spend time refactoring.
If your code base for selectors are clean and free of dependencies, definitely look into using reselect resultFunc. More documentation here: https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc
Code I used to mock the createSelector
// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
createSelector: (...params) => params[params.length - 1]
}));
Then to access the created selector, I had something like this
const myFunc = TargetSelector.IsCurrentPhaseDraft;
The entire test suite code in action
// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
createSelector: (...params) => params[params.length - 1]
}));
import * as TargetSelector from './TicketFormStateSelectors';
import { FILTER_VALUES } from '../../AppConstant';
describe('TicketFormStateSelectors.IsCurrentPhaseDraft', () => {
const myFunc = TargetSelector.IsCurrentPhaseDraft;
it('Yes Scenario', () => {
expect(myFunc(FILTER_VALUES.PHASE_DRAFT)).toEqual(true);
});
it('No Scenario', () => {
expect(myFunc(FILTER_VALUES.PHASE_CLOSED)).toEqual(false);
expect(myFunc('')).toEqual(false);
});
});
I've tried to search for answer to this problem for some time now and I failed. So I decided to give it a try here. If there is a such question already and I missed it, I'm sorry for duplicate.
Assume I have this javascript module 'myValidator.js', where are two functions and one function calls the other one.
export const validate = (value) => {
if (!value.something) {
return false
}
// other tests like that
return true
}
export const processValue = (value) => {
if (!validate(value)) {
return null
}
// do some stuff with value and return something
}
Test for this like this.
I want to test the validate function, whether is behaves correctly. And then I have the processValue function that invokes the first one and returns some value when validation is ok or null.
import * as myValidator from './myValidator'
describe('myValidator', () => {
describe('validate', () => {
it('should return false when something not defined', () => {
...
}
}
describe('processValue', () => {
it('should return something when value is valid', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => true)
expect(validate('something')).toEqual('somethingProcessed')
}
it('should return null when validation fails', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => false)
expect(validate('somethingElse')).toEqual(null)
}
}
}
Now, the problem is that this doesn't actually work as processValue() actually calls the function inside the module, because of the closure I suppose. So the function is not mocked as only the reference in exports is changed to jest mock, I guess.
I have found a solution for this and inside the module to use
if (!exports.validate(value))
That works for the tests. However we use Webpack (v4) to build the app, so it transforms those exports into its own structure and then when the application is started, the exports is not defined and the code fails.
What's best solution to test this?
Sure I can do it with providing some valid and invalid value, for this simple case that would work, however I believe it should be tested separately.
Or is it better to not mock functions and call it through to avoid the problem I have or is there some way how to achieve this with JavaScript modules?
I finally found the answer to this question. It's actually in the the Jest examples project on GitHub.
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* This file illustrates how to do a partial mock where a subset
* of a module's exports have been mocked and the rest
* keep their actual implementation.
*/
import defaultExport, {apple, strawberry} from '../fruit';
jest.mock('../fruit', () => {
const originalModule = jest.requireActual('../fruit');
const mockedModule = jest.genMockFromModule('../fruit');
//Mock the default export and named export 'apple'.
return {
...mockedModule,
...originalModule,
apple: 'mocked apple',
default: jest.fn(() => 'mocked fruit'),
};
});
it('does a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked fruit');
expect(defaultExport).toHaveBeenCalled();
expect(apple).toBe('mocked apple');
expect(strawberry()).toBe('strawberry');
});
I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).
I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.
My search.js file
export {
search,
}
function search(){
// does things
return Promise.resolve('foo')
}
My react component:
import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'
export default SearchContainer {
constructor(){
this.state = {
query: "hello world"
}
}
componentDidMount(){
search(this.state.query)
.then(result => { this.setState({ result, })})
}
render() {
return <SearchResults
result={this.state.result}
/>
}
}
In my unit tests, I want to check that the method search was called with the correct arguments.
My tests look something like that:
import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';
import SearchResults from './SearchResults';
let mockPromise;
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise)};
});
import SearchContainer from './SearchContainer';
describe('<SearchContainer />', () => {
it('should call the search module', () => {
const result = { foo: 'bar' }
mockPromise = Promise.resolve(result);
const wrapper = shallow(<SearchContainer />);
wrapper.instance().componentDidMount();
mockPromise.then(() => {
const searchResults = wrapper.find(SearchResults).first();
should(searchResults.prop('result')).equal(result);
})
})
});
I already had a hard time to figure out how to make jest.mock work, because it requires variables to be prefixed by mock.
But if I want to test arguments to the method search, I need to make the mocked function available in my tests.
If I transform the mocking part, to use a variable:
const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
return { search: mockSearch};
});
I get this error:
TypeError: (0 , _search.search) is not a function
Whatever I try to have access to the jest.fn and test the arguments, I cannot make it work.
What am I doing wrong?
The problem
The reason you're getting that error has to do with how various operations are hoisted.
Even though in your original code you only import SearchContainer after assigning a value to mockSearch and calling jest's mock, the specs point out that: Before instantiating a module, all of the modules it requested must be available.
Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.
One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.
Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mock call. So, the order of relevant operations will be something like:
Set a mock factory for ./search.js
Import all dependencies, which will call the mock factory for a function to give the component
Assign a value to mockSearch
When step 2 happens, the search function passed to the component will be undefined, and the assignment at step 3 is too late to change that.
Solution
If you create the mock function as part of the mock call (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.
As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.
Since you now know jest mocking actually happens before imports, a trivial approach would be:
import { search } from './search.js'; // This will actually be the mock
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise) };
});
[...]
beforeEach(() => {
search.mockClear();
});
it('should call the search module', () => {
[...]
expect(search.mock.calls.length).toBe(1);
expect(search.mock.calls[0]).toEqual(expectedArgs);
});
In fact, you might want to replace:
import { search } from './search.js';
With:
const { search } = require.requireMock('./search.js');
This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).
Additional note
All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as #publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:
import * as search from './search.js';
beforeEach(() => {
search.search = jest.fn(() => mockPromise);
});
In my case, I got this error because I failed to implement the mock correctly.
My failing code:
jest.mock('react-native-some-module', mockedModule);
When it should have been an arrow function...
jest.mock('react-native-some-module', () => mockedModule);
When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...
...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');
jest.mock('../../api/someApi.js', () => {
return {
GetSomeData: jest.fn()
};
});
beforeEach(() => {
// Clear any calls to the mocks.
SomeApi.GetSomeData.mockClear();
});
it("renders valid token", async () => {
const responseValue = {data:{
tokenIsValid:true}};
SomeApi.GetSomeData.mockResolvedValue(responseValue);
let wrapper = shallow(<MyPage {...props} />);
expect(wrapper).not.toBeNull();
await wrapper.update();
const state = wrapper.instance().state;
expect(state.tokenIsValid).toBe(true);
});