18

I'm trying to test the template of my Vue app after making an ajax request which is changing one variable of the component' data. This variable (books) is use to conditional render the gallery

CONTEXT: I want to create a gallery in order to show the books I have stored in my back end. For this, I fetching my books on mounting the component. The result of this is set in the variable books. What I'm trying to test is that, after the ajax call, the component renders the gallery with the books

PROBLEM: When the books variable is set, the div <div v-else-if='books.length > 0'>SHOW GALLERY</div> should be rendered, but the "else" div (<div v-else class='loader'>Loading</div>) is still rendered

The next two blocks of code are the component and the test itself:

BookGallery.vue (component I'm testing)

 <template>
      <v-content>
          <v-container fluid>

               /*** Conditional rendering: After the ajax request books > 0, so this div should be rendered ***/
              <div v-if='books.length > 0'>SHOW GALLERY</div>

              <div v-else class='loader'>Loading</div>
          </v-container>
      </v-content>
    </template>

    <script lang='ts'>
      import {Component} from 'vue-property-decorator';
      import {MyMixin} from '../mixin';

      @Component({components: {BookInformation}})
      export default class BookGallery extends MyMixin {
          public books: string[] = [];

          public async mounted() {

               /*** books is set as the result of the ajax request ***/

               this.books = (await this.$http.get(this.host + '/books')).data;
          }
      }
    </script>

    <style scoped lang='scss'></style>

TEST

    @test
    public async 'after calling the books, the gallery show all of them'() {

        /*** MOCKING THE RESPONSE ***/
        TestCase.OK_200({
            books: [
                { uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
                { uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
            ],
        });


        /*** MOUNTING MY COMPONENT ***/
        const wrapper = TestCase.shallowMount(BookGallery);


        /** ASSERTING **/
        await flushPromises().then(() => {

            /**  the first "expect" passes, so books > 0 = true **/
            expect(wrapper.vm.$data.books).to.eqls({
                books: [
                    { uri: 'img/covers/1.jpg', title: 'El Prinicipito'},
                    { uri: 'img/covers/2.jpeg', title: 'The Lord of the Rings'},
                ],
            });

            /** This is failing. The application should read 'SHOW GALLERY' when books > 0 (something tested in the previous assert), as explained in the first comment of the component's template, but is not updating the dom, only the data **/
            see('SHOW GALLERY');
        });
    }

The QUIESTION: How can I update my DOM for my very last assert -see("SHOW GALLERY")-?

UPDATE

see Function The function only searches for a HTML element in the wrapper that vue-test-utils is using for mounting the application. In this case, as I have left it null, it is searching the text "SHOW GALLERY" over the whole HTML file

export const see = (text: string, selector?: string) => {
    const wrap = selector ? wrapper.find(selector) : wrapper;

    expect(wrap.html()).contains(text);
};
Fran Roa Prieto
  • 385
  • 1
  • 3
  • 12

4 Answers4

20

I just solved a similar problem, using shallowMount and its sync: false option.

This deactivates sync rendering, requiring you to give the renderer some time to perform its job (await wrapper.vm.$nextTick()) where needed. Once this done, the component was re-rendered based on my reactive data, as expected.

Brice Pernet
  • 201
  • 2
  • 4
16

I ran into very similar problem and solved it with await wrapper.vm.$forceUpdate(); before assertion that failed. This forces Vue to update view.

Lukáš Irsák
  • 1,092
  • 1
  • 14
  • 23
11

Initially Vue Test Utils run updates synchronously. But later they removed sync mode. Here they explain reasons and show how to write tests:

Test code will change from this:

it('render text', (done) => {
    const wrapper = mount(TestComponent)
    wrapper.trigger('click')
    wrapper.text().toContain('some text')
})

To this:

it('render text', async () => {
    const wrapper = mount(TestComponent)
    wrapper.trigger('click')
    await Vue.nextTick()
    wrapper.text().toContain('some text')
})

So, to fix your issue you need to add await wrapper.vm.$nextTick() before assertion see('SHOW GALLERY');

Lana
  • 1,199
  • 7
  • 16
  • From where coming the `Vue` var ? – Albert Hidalgo Oct 13 '22 at 00:34
  • 1
    @AlbertHidalgo `import Vue from 'vue'` or you can also use the method from the wrapper: `await wrapper.vm.$nextTick()`. Or it's also possible just await the trigger itself: `await wrapper.trigger('click')` See the docs: https://v1.test-utils.vuejs.org/guides/#testing-asynchronous-behavior – Lana Oct 14 '22 at 13:20
2

I had a similar problem where child components markup was not rendered on mount and based on the other answers the following worked for me:

test('it works as expected', async () => {
  const wrapper = await mount(TestComponent);
  //expect(...)
});
Ben
  • 767
  • 8
  • 14