An updated approach to testing vuex actions - vue.js

In the "Testing Actions" documentation, there is a recommendation to use inject-loader in order to mock entire module imports, and their respective dependencies, when unit testing.
For example, you can mock the ../api/shop import found inside the ./actions file with the following:
const actionsInjector = require('inject-loader!./actions')
// create the module with our mocks
const actions = actionsInjector({
'../api/shop': {
getProducts (cb) {
setTimeout(() => {
cb([ /* mocked response */ ])
}, 100)
}
}
})
Unfortunately, that package does not support Webpack 5.
Is there an updated approach, or alternative package, that is recommended?

Thanks #Estus Flask.
The clarification/mixup was that, whilst I was using vi.mock, and it's auto-mocking algorithm :
"...will mock the module itself by invoking it and mocking every
export."
i.e. it still runs the import statements. To prevent this, a factory must be provided (see documentation), e.g.:
vi.mock('module', <factory-here>)
Which then replaces the entire file import.

Related

Nuxt avoid import of client-side script for server-side rendering

In my nuxt.js application, I have a script that imports an NPM package which is only compatible with browser contexts (it references document, location, window, etc.)
Is there a way to exclude this from SSR?
import thing from "#vendor/thing"; // causes `document not defined` error
export default showThing(){
if (process.client) {
thing();
}
}
I can use the method with process.client but this file is still imported in my components.
You could import it dynamically rather than in every context.
As explained in my answer here: https://stackoverflow.com/a/67825061/8816585
In your example, that would be something like this
export default showThing(){
if (process.client) {
const thing = await import('#vendor/thing')
thing()
}
}

Nuxt class-based services architecture (register globally; vs manual import)

In my Nuxt app I'm registering app services in a plugin file (e.g. /plugins/services.js) like this...
import FeatureOneService from '#/services/feature-one-service.js'
import FeatureTwoService from '#/services/feature-two-service.js'
import FeatureThreeService from '#/services/feature-three-service.js'
import FeatureFourService from '#/services/feature-four-service.js'
import FeatureFiveService from '#/services/feature-five-service.js'
export default (ctx, inject) => {
inject('feature1', new FeatureOneService(ctx))
inject('feature2', new FeatureTwoService(ctx))
inject('feature3', new FeatureThreeService(ctx))
inject('feature4', new FeatureFourService(ctx))
inject('feature5', new FeatureFiveService(ctx))
}
After doing this I can access any of my service on vue instance like this.$feature1.someMethod()
It works but I've once concern, that is, this approach loads all services globally. So whatever page the user visits all these services must be loaded.
Now I've 20+ such services in my app and this does not seem optimal approach to me.
The other approach I was wondering is to export a singleton instance within each service class and import this class instance in any component which needs that service.
So basically in my service class (e.g. feature-one-service.js) I would do like to do it like this..
export default new FeatureOneService() <---- I'm not sure how to pass nuxt instance in a .js file?
and import it my component where it is required like so...
import FeatureOneService from '#/services/feature-one-service.js'
What approach do you think is most feasible? if its the second one, then how to pass nuxt instance to my singleton class?
Yep, loading everything globally is not optimal in terms of performance.
You will need to either try to use JS files and pass down the Vue instance there.
Or use mixins, this is not optimal but it is pretty much the only solution in terms of reusability with Vue2.
Vue3 (composition API) brings composables, which is a far better approach regarding reusability (thing React hooks).
I've been struggling a lot with it and the only solution is probably to inject services to the global Vue instance at the component/page level during the initialisation (in created hook), another option is to do that in the middleware (or anywhere else where you have access to the nuxt context. Otherwise you won't be able to pass nuxt context to the service.
I usually set up services as classes, call them where necessary, and pass in the properties of the context which the class depends on as constructor arguments.
So for example, a basic MeiliSearchService class might look like:
export class MeilisearchService {
#client: MeiliSearch
constructor($config: NuxtRuntimeConfig) {
super()
this.#client = new MeiliSearch({
host: $config.services.meilisearch.host,
apiKey: $config.services.meilisearch.key
})
}
...
someMethod() {
let doSomething = this.#client.method()
...
}
...
}
Then wherever you need to use the service, just new up an instance (passing in whatever it needs) and make it available to the component.
data() {
const meiliSearchService = new MeiliSearchService(this.$config)
return {
meiliSearchService,
results,
...
}
},
methods: {
search(query) {
...
this.results = this.meiliSearchService.search(query)
...
}
}
As I'm sure you know, some context properties are only available in certain Nuxt life-cycle hooks. I find most of what I need is available everywhere, including $config, store, $route, $router etc.
Don't forget about Vue's reactivity when using this approach. For example, using a getter method on your service class will return the most recent value only when explicitly called. You can't, for example, stick the getter method in a computed() property and expect reactivty.
<div v-for='result in latestSearchResults'>
...
</div>
...
computed: {
latestSearchResults() {
return this.#client.getLatestResults()
}
}
Instead, call the method like:
methods: {
getLatestResults() {
return this.#client.getLatestResults()
}
}

How to mock vuex functions with no direct access?

So I have run into quite a test problem.
To Summarize:
A while back I created a private npm Module, that manages and create a vuex store and injects it as a module into the main vuex store.
Now I want to mock a specific function of that vuex module in my test but that doesn't work.
I tried this:
store.getters.getSomething = jest.fn(() => "stuff);
But that results in this error:
TypeError: Cannot set property getSomething of #<Object> which has only a getter
Now did a little digging, and I here you can see where this getter comes from:
First there is a simple vuex module definition:
//vuexModule this is not accessible outside of the npm module
getters:{
getSomething: (state)=>{ return "stuff"}
}
export { getters }
Now this is imported into this class:
//storeProvider //this is exposed by the npm package
import vuexModule from "./vuexModule";
export default class StoreProvider {
constructor(vuexStore, vuexModuleName = "someStore") {
this.vuexStore = vuexStore;
this.vuexStore.registerModule(vuexModuleName, VuexConfigStore);
}
So this class will generate a vuex module using the previously defined module.
Now the whole thing is added to the main vuex store in the actual application.
Note: this code is now the actual application and not in the npm package.
import StoreProvider from "someNpmModule";
import mainStore from "vuexPlugin";
const storeProvider = new StoreProvider(store);
//later in main:
new Vue({ mainStore })
So the question is: How can I mock getSomething?
I know the code is quite abstracted, but I can't show all the code, and its probably too much for stackoverflow.
The main point is, that I can just import the getter and mock it like this:
import {getSomething} from "...";
getSomething = jest.fn(() => "stuff);
//Or this:
store.getters.getSomething = jest.fn(() => "stuff);
I found this stackoverflow question: TypeError during Jest's spyOn: Cannot set property getRequest of #<Object> which has only a getter
But as the npm package is rather big I can't just mock the whole package for the unit tests, as it provides some necessary pre-conditions. I tried genMockFromModule(), but I still need to target this specific function.
And as mentioned I can't simply import the function.
Any ideas?

Meteorjs test Meteor methods

I have the following simple Meteor Method that I want to test.
It inserts a given Object into my collection
Meteor.methods({
insertHelper(profile){
HelperCollection.insert(profile);
return true;
},
}
For Testing i use "dispatch:mocha-phantomjs"
My Test so far is the following:
describe('methods', () => {
it('can delete owned task', () => {
Meteor.call('insertHelper',{a: 1});
});
});
When running my tests I get the message " Error: Method 'insertHelper' not found [404]"
So how can I access my Meteor methods from my Test suite?
As discussed in the comments, we have to include the files where the Meteor methods are defined in order to test them :
import '/path/to/method/file.js';
or with require :
require('/path/to/methos/file.js');
EDIT
Meteor advises to use import instead of using require, if you can.

How to dynamically mock ES6 modules with SystemJS?

I have a single-page application written in ES6. The code in transpiled server-side into classic javascript by babelJs, then loaded by SystemJs.
Javascript present in my html file:
System.config({
baseURL: '/js',
meta: {
'/js/*': { format: 'cjs' }
}});
System.defaultJSExtensions = true;
System.import("index.js")
.catch(function (error) {
console.error(error)
});
index.js:
import f1 from 'file1';
import f2 from 'file2';
// code here ...
Everything works fine. index.js is loaded, and all import statements are correctly executed.
Now, I want to create some pages with mocked ES6 modules, for testing purpose. My goal is to display pages by replacing model classes (contained in ES6 modules) with other static test classes.
Let's say I have 3 files: real_model.js, fake_model.js and component.js. component.js import the real model (import Model from 'real_model';).
How can I replace the real model by the fake one (in the component) dynamically ?
It's been a while since this question was posted, but maybe this solution might still be of help to anyone else.
With SystemJS it is possible to create a module on-the-fly using System.newModule. Then you can use System.set to overwrite existing modules with the new one. In our tests we use the following helper function to mock existing modules:
function mockModule(name, value) {
const normalizedName = System.normalizeSync(name);
System.delete(normalizedName);
System.set(normalizedName, System.newModule(Object.assign({ default: value }, value)));
}
Then, e.g. inside the beforeEach callback, we assign the mock and then import the module to be tested using System.import:
let [component, fake_model] = [];
beforeEach(() => {
// define mock
fake_model = { foo: 'bar' };
// overwrite module with mock
mockModule('real_model', fake_model);
// delete and reimport module
System.delete(System.normalizeSync('component'));
return System.import('src/testing').then((m) => {
component = m.default;
}).catch(err => console.error(err));
});
// test your component here ...
A big advantage of this approach is that you don't need an additional mocking library and it works solely with SystemJS.