#vue/test-utils :: Testing apis called in Store - vue.js

In my Vue3 app, I have a page which depends on a Vue Store to populate data. The Vue store uses PatientService which makes an API call, to populate the store.
While writing the Unit Tests, I am mocking the API via jest like so:
import PatientService from "#/core/store/services/patientTableService";
jest.mock('#/core/store/services/patientTableService')
PatientService.mockResolvedValue('mock value'))
If I want to mount the component inside my unit test, with the state populated by the mock API response, how do I mount it?
Is it like so:
const wrapper = mount(PatientList,{
plugins: [store]
})
where store is the original store js file?
Or do I mount using some other method? I want that when the component is mounted for testing; everything inside the store should remain the same except the store being populated based on the Mock API response, and not the real one.

Related

Using Vue Composition API with Pinia

I have a Vue 3 project and I'm working with Composition API. I'm communicating with my backend using Urql which is a graphql library which allows me to wrap API requests as composables.
I'm new to Pinia, but after a bit of time working with Vue 2 + Vuex I can tell that one of the most common scenarios of writing actions was making API requests and updating the state (asynchronously) with the response. I'm trying to adopt the same technique in my current tech stack and facing some issues.
My problem is that I can't just use the old fashioned fetch/axios/got libraries to execute requests whenever I want, I should first register the composable somewhere. I've seen that one option is to call the composable's use* function in the store's state section, but it seems weird to me to have such thing in there as it has nothing to do with the state. I've tried executing the use* method directly in an action, but it seems to fail.
I wonder if I'm missing some best-practice way to work with Urql and Pinia, as things are getting more and more complex even though my usecase is pretty common and simple. Should I use Urql's Client directly? Any other good solution to make gql requests from within my store actions?
export const useUsersStore = defineStore('app', {
actions: {
setUser() {
const response = (await useUsers()).data // this wont work
},
}
);

Why use Vuex in SSR?

My question is, what's the point of setting up Vuex for the server when the state will be overwritten when the client side hydration takes place?
I have some data (Helm env variables) that I want to store in the vuex store for later use.
These variables is only available to me on the server, so I started trying to add them to the store in my createApp script when running on the server.
The store state however is reset when the client side hydration kicks in, so no env variables left.
Google told me I should use like window.INITIAL_DATA to set the state again on the client:
store.replaceState(window.INITIAL_DATA)
But if have to use the window object to pass store data to the client, what's the point of using Vuex on the server at all?
Isn't it better to skip Vuex overhead on the server and just use Vuex on the client and populate it with INITIAL_DATA?
I'm probably missing something..
https://ssr.vuejs.org/guide/data.html#data-store
During SSR, we are essentially rendering a "snapshot" of our app. The asynchronous data from our components needs to be available before we mount the client side app - otherwise the client app would render using different state and the hydration would fail.
To address this, the fetched data needs to live outside the view components, in a dedicated data store, or a "state container". On the server, we can pre-fetch and fill data into the store while rendering. In addition, we will serialize and inline the state in the HTML after the app has finished rendering. The client-side store can directly pick up the inlined state before we mount the app.
Also to mention:
The data you access while SSR in any Component needs to come from somewhere if you want to share information across Components, this is what Vuex is there for.

How to test Vue "Services" mounted to root, accessed via Vue.prototype

First, I'd like to explain that I have a Vue component repository that is responsible for displaying data retrieved from an http service. Rather than the component itself managing the same data retrieval per instance and spamming the client with network requests, I've managed to find a solution which allows another component to be mounted to the root directly (which I've dubbed as a "Service" due to its similarity to Angular) to manage the data those components need instead. This works great and other components can access it via Vue.prototype (via this.$TestService.value). It has some caveats but for the most part it accomplishes exactly what I needed. This may be uncommon, but those that use Vuex are using a similar methodology and I don't want to use the store paradigm.
I've made a very simple Vue JsFiddle to show this in action...
https://jsfiddle.net/spronkets/8v31tcfd/18
Now, to the point... I'm using #testing-library/vue, #vue/test-utils, and Jest to test the components and get test coverage and now I get errors anytime I run the tests due to the service not existing on the Vue.prototype during the test execution. I don't want to mock out the functionality of the "Service" layer, so does anyone have a solution to test these root-mounted components? I've tried manually exporting the services (unmounted and mounted) and including them in the mock section as well as importing the files directly into the test files but the "Service" is always undefined when the component is trying to retrieve the value and ONLY during test execution...
I've also created a simple repository modelled after the Vue component repository I am working with below...
https://github.com/kcrossman/VueServiceExample
To get started, clone the repo and follow the README.md included in the repo. Thanks!
I would go against using the real service if it is asyncronous, but if you just want to register it to be available you can follow the mock instructions but instead of mocking with an object just import the real service. Although after seeing your TestService implementation you will need to separate the real service from the service registration and export it to be able to register it in local vue.
You need to create and prepare your custom Vue instance in your tests in order to use any custom functionalities in your unit tests (like stores, routers, and anything else). (You can use your real modules with the custom instance, don't have to mock anything.)
In your case you should create a new Vue instance with "createLocalVue" function from '#vue/test-utils' and apply your custom prototype functionalities on that. After that you can write proper test cases accessing that custom features as well.
Update:
For those that might be referring to this in the future, Vue Plugins might be a better solution for this kind of functionality.
I stumbled along this issue in GitHub and that led me to the fix I made below:
https://github.com/testing-library/vue-testing-library/issues/113
Specifically, this comment by user nikravi:
ok, I found the fix. The trick was to add
import Vue from "vue";
import Vuetify from "vuetify";
Vue.use(Vuetify);
and then the render() works without warnings.
After I manually imported Vue and set Vue.prototype.$TestService = TestService directly in the unit test, it got passed that error. Personally, I think this is pretty silly, but it worked.
After this worked, I also found that you can access the Vue instance directly within the render callback (from #testing-library/vue), so I finished on this code instead of importing Vue:
render(TestComponent, {}, vue => {
vue.prototype.$TestService = TestService;
});
I've included all the commits to solve my issue in the repo I posted previously:
https://github.com/kcrossman/VueServiceExample
Some of the tests were malformed but once I made those changes, the tests started to work and I updated some other files to be a bit nicer for people to refer to.

Dynamically register a Vuex plugin?

How can I register a plugin dynamically to vuex. The documentation says I can dynamically register modules etc. but not how to do it for a plugin.
Is including the plugin at store creation the only method to add plugins?
I was hoping for a store.use(plugin) or vuex.use(plugin)…
A Vuex plugin is simply a function that receives the store as the only argument, and is invoked in the Store instance during construction.
To apply a plugin after a Store has been constructed, you just need to invoke the plugin function and pass the Store instance to that function:
import Vuex from 'vuex'
import Plugin from 'plugin'
const store = new Vuex.Store({ ... })
// Later on
Plugin(store)
Keep in mind that some plugins simply may not work correctly with already-constructed Store instances. Your milage may vary.

Vue js 2 Persist a component's data *privately*

I currently have a Vue.js 2 SPA using Vuex for inter-component communication and Vuex-persisted state to persist my shared state.
However, I want to persist a component's local private data.
I tried the package vue-persist and it persists the component data fine, however, the variables are not namespaced and therefore shared between all component instances. I want to persist each component's data separately.
Does anyone know how I can achieve these 2 things in my Vue.js 2.x app?
1) Persistence of my local data (so it survives a browser refresh).
2) The persisted data is not shared with other components (perhaps name-spaced for each component instance?).
Thanks!
I'm not familiar with vue-persist, but it looks like you can pass a store name as the second argument to $persist. Choose a unique store name for the component instance (this will vary depending on the component).
e.g.
created() {
this.$persist(['foo'], 'my-comp:' + this.id);
}