Search code examples
unit-testingvue.jsjestjsvuexvue-test-utils

Vue Unit Testing: How to test complex components with props, vuex store, watchers, getters etc


I have a question about unit testing (Jest) within a vue app - I guess it is rather an architectural question, but maybe specific code examples could also help me out.

I have sketched out a more or less complex setup of vue components from my app. They are still simplified but I guess they should help to describe the situation.

Vue Parent and Children components (PDF Attachment for those preferring non-pixel data :)

I have a Wrapper which has three child components:

  • Search (Input field to search on Map)
  • List (List of stores, represented also as markers on map)
  • Map (Google Map itself)

I would really like to write some unit tests for this, but it seems so super tedious to setup those tests, so that I doubt that this is the way to do it.

It starts with the way I load the google maps API: The wrapper loads it asynchronously, sets API key etc. As soon as the google map API is loaded it passes it down to the children.

Of course the map only really starts to load, create and display markers, as soon as google is present and I can use new this.google.maps.Map() for example.

Even if I would like to test some methods of the Map component, I have to provide an actual google object, because otherwise the whole init process in mount() fails. It can't make a map.

So do I have to load the actual google's map API in my tests? Or could I somehow skip mounting the map but still test a few isolated methods?

Further you can see that my Map watches a getter from the vuex store: Whenever the searchCoordinates set by Search change in my vuex store, I want to execute some functions (for example center my map to the new search coordinates or remove a current location marker if the search did not return valid searchCoordinates).

How could I somehow break down the interlinking between these components, but still write good and helpful tests, which help me to guarantee that the app does what it is supposed to do?

I know that I can use a vuex store within my tests (Medium article about vuex testing). But still I feel like the whole app is so much inter-connected that doing actual unit testing – testing one small piece at a time – is impossible. It seems like that I would have to mount not only Map alone, but also the other components and a vuex store with actual data, to really write bulletproof tests.

A few other examples, where I struggle:

  • In my mounted() I subscribe to an action with this.$store.subscribeAction((action) => {... - of course $store is undefined in my tests. Should I mock it?
  • As I said I am watching a getter: This throws errors like TypeError: Cannot use 'in' operator to search for 'searchCoordinates' in undefined. Would I have to mock all the getters as well? If so: what remains to be tested? If I mock (~=leave away) all the vuex stuff, it seems to me, that testing becomes useless...

But maybe I am mistaken here. Also I am quite new to unit testing, so any hints are very welcome. Thank you very much in advance. I hope somebody has some valuable input. All those examples (even in books like Vue.js - up and running) are always broken down to very simple and understandable examples, but on the other hand don't really reflect real-life apps...

Cheers

----- Edit:

I got a needs more focus vote by someone. So I try to sum up simple and small questions:

Summary of Questions

  1. Does somebody have a general input on how to test larger and more complex vue applications?
  2. Is there a way to test a component with a google map (or a component that is relying heavily on any third party code)
  3. Is mocking getters, store and actions a good idea in this case? (I have my doubts there).
  4. For testing watched properties from the vuex store, I read in this thread that mapState should be mocked. (in my case probably my getters). Does this make sense to mocke those getters in my case?
  5. Is it best practice to simplify as many things as possible in an tested component (for example, I started to override some methods, which failed (the one including google map initiation) to just get my test up and running:
const initMapMock = jest.fn();
const initMarkersMock = jest.fn();
...
const wrapper = shallowMount(Map, {
    methods: {
        initMap: initMapMock, // replace initMap(), which needs google map API
        initMarkers: initMarkersMock,
      },
    ...

Does that make sense?


Solution

  • After diving deeper into this question I realized one thing: If one has such a complex situation like described above, unit testing is probably not the right tool for this!

    In this case end to end testing also called integration testing would be much more suitable for the job.

    Because what I actually want to test is, that all the different component react in the right way, if something is changed/triggered and so on, I need to see the whole picture.

    If I go back to my example I want to see that the map changes if the search input registers a change and so on.

    I ended up integrating a few tests for this by using a headless browser googles puppeteer, opening the webpage, filling in a search query and then search the DOM for reliable changes in the map for example.

    Unfortunately I did not have the time to really go into detail with the testing on this one because of budget restrictions, but I got an idea of how this should be approached.

    My initial ideas of mocking a store etc. would require so much work which would be nothing else than building a big mock which resembles the rest of the app around a unit test, that it would be downright stupid to do that. With integration tests I use this already built app around my test, but I have the advantage of being able to test everything in sync!

    I personally also think that integration tests are much more powerful than unit tests, because they illuminate the parts of an application that are not so easy to grasp by a developer and will throw errors if one part of the application is changed and the other fails to function as desired.

    If somebody has more to add, I am still very glad to hear opinions on that matter.

    Cheers