Mocking a namespaced getter in vue testing library - vue.js

I want to mock a VueX store getter.
I tried following the examples on the vue testing library examples page:
https://github.com/testing-library/vue-testing-library/blob/main/src/__tests__/vuex.js
But it turns out mocking a store object is no longer an option, as I'm getting the following error:
Providing 'store' or 'routes' options is no longer available.
You need to create a router/vuex instance and provide it through 'global.plugins'.
So I do that like so:
const item_page = render(ItemPage, {
global: { plugins: [ store, router ]},
});
Then I try to access the specific getter that I wish to mock.
If I console.log(site.getters) I get an object with all the getters from all the store modules. And I have no problem referencing getters that are not namespaced, like so
site.getters.my_getter
or
site.getters['my_getter']
But when I try to reference a namespaced one, for example:
site.getters['module_one/my_other_getter']
this syntax returns 'undefined'
Any ideas how I could refernce these namespaced getters so that I could then assign a mock function to them?

Related

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?

How to access Vuex state in a seperate non-Vue component, JavaScript/TypeScript file?

Something we would like to do is keep as much business logic away from UI logic. Particularly...like to setup a service type Typescript class that can interact with some Node Module API and update the apps state in Vuex.
Rather than having Vue components directly worry about this business logic.
Is there a way I can interact with Vuex using this vuex-class library decorators in a separate just .ts/.js file? So like creating some ES class, and having a store, getter, mutations, etc in it to interact with my Vuex state?
//fooService.ts .......or something like that
import ...
export default class FooService {
#FooModule.State
private FooModel!: FooModel;
#FooModule.Getter
private foo!: Foo;
#FooModule.Mutation
private setFoo!: (newFoo: Foo) => void;
private doFooBusinessLogic(){ ... }
}
And then in the Vue component just initializing this class and calling it to interact with this Vuex data.
Rather than having to do ALL interactions with Vuex within a Vue component.
I was able to accomplish this by using this library instead, https://github.com/gertqin/vuex-class-modules
The README describes it well...
import { userModule } from "path/to/user-module.ts";
And then you use the Vuex module mutations etc with userModule.someMutation()

Vue component Vuex option block feature

While reading Vuex repository docs, I came across the following syntax:
export default {
template: '...',
data () { ... },
// NOTICE SYNTAX BELLOW
vuex: {
getters: {
count: function (state) {
return state.count
}
}
}
}
Notice the syntax of vuex option block of the component.
When referencing to either the official Vuex docs or official Vue 2 API docs, the usage of vuex component option, smiliar to the one above, is not mentioned.
The only thing I understand about this block is (according to Vuex repository docs):
Note the special vuex option block. This is where we specify what state the component will be using from the store.
What is the actual usage of vuex block? can it be used instead of component binding helpers? such as mapGetters and mapState?
Seems like the official docs are lack of docs about this feature.
I'd like to have further information about this feature, thank you.
It think it is pointing out the case that one can individually decide on a component basis whether vuex shall be used or not.
The initial example in the vuex docs injects the store on a global level into all Vue instances, meaning that all components have access to the store.
If you'd like to have more control over which component uses vuex you can go for a explicit declaration of using vuex - for each component individually by using the syntax you are referring to.

Accessing vue-axios inside Vuex module actions

In my Vuex setup, the store is currently structured like this:
store
- modules
- common_data
- index.js
- mutations.js
- actions.js
- getters.js
Now, one of the actions inside actions.js is defined as:
populateTimeZones(context) {
var baseUrl = context.getters.getApiBaseUrl;
this.$http.get(baseUrl + '/time-zones')
.then(function (res){
if(res.status == 'ok'){
this.$store.commit('SET_TIME_ZONES', res.info.timeZones);
} else {
alert('An error occurred. Please try again!');
return;
}
}.bind(this));
}
I think the Vue instance is not available here, which is causing the action to fail with the error: Cannot read property 'get' of undefined. I've tried other combinations like this.axios and vue.axios but the result is same.
Am I doing something wrong? What is the common pattern for handling such cases?
You won't be able to access the Vue instance without passing it through or creating a new one.
A simple way to do what you want is to simply pass your this through to your action this.$store.dispatch('populateTimeZones', this), then change your method signiture to populateTimeZones({ context }, vueInstance). This would then allow you to access vueInstance.$http.
Another idea would be something called vue-inject which is a DI container for Vue. It would allow you to register axios as a service and then simply call VueInjector.get('axios') whenever you need it. On the Vue component itself you can do dependencies: ['axios'] and then use like this.axios. Not so much an issue in this case but helpful when you have lots of services doing work for you