0

I have a service, GetStatsService that contains an async method that calls several api endpoints and then processes the data, returning an object. It looks something like this:

export default() {
  async getMonthlyStats (userId) {
    const calls = [axios.get(...), axios.get(...)]
    const [stats, results, user] = await Promise.all(calls)
    const combinedStats = {}
    ...
    ...
    return combinedStats
  }
}

The getMontlyStats method is then called in a component called UserComparison.vue, where it gets called multiple times for each route query parameter representing the user id. The UserComparison component also has a data property on it called stats, which is an array and where the service call result for each player gets pushed:

async fetch() {
  let calls = []
  this.$route.query.user.forEach((id) => {
    calls.push(this.fetchUserStats(id)
  }
  try {
    await Promise.all(calls)
  } catch (err) {
    console.log(err)
  }
}
async fetchUserStats(id){
  const call = await GetStatsService.getMonthlyStats(id)
  this.stats.push(call)
}

Finally, the this.stats data property is then passed as a prop to a child component, StatsTable.vue.

My problem(s): I want to unit test the service, but fail to do so in any way I can think of. I tried creating a test for the child component. There, in the beforeEach() method, I mocked the api calls with moxios.

beforeEach(() => {
  moxios.install(axios)
  moxios.stubRequest(new RegExp(`${base_api}/users/.*/stats`), {
     status: 200,
     response: SampleStats
  })
  moxios.stubRequest(new RegExp(`${base_api}/users/.*/history`), {
     status: 200,
     response: SampleHistory
  })

  const userIds = [ '123', '456', '789']
  const stats = []
  userIds.forEach(async (id) => {
    stats.push(await GetStatsService.getMonthlyStats(id))
  }

  wrapper = mount(StatsTable, {
    localVue,
    propsData: {
      stats
    },
    mocks: {
      $t: (t) => { return t }
    },
    attachToDocument: true
})

})

I tried using vm.$nextTick() to wait for the async service to return the value and then push it to the stats const. I tried using flush-promises to resolve all promises and then push the async call result to the stats array. Nothing seems to work. The stats prop is always an empty array. I am aware that testing asynchronicity in Vue can be tricky, so I assume I don't fully understand something.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Iulia Mihet
  • 650
  • 1
  • 10
  • 34
  • You should keep tests clean and simple, therefore it makes sense to test `UserComparison.vue` and `StatsTable.vue` independently of each other. There you e. g. test if `UserComparison` dispatches the action to fetch or if `StatsTable` renders the right amount rows based on the passed props (which you declare static in the test). – Bennett Dams Nov 01 '18 at 17:21

1 Answers1

0

I am still having the problem described above but I did figure out that if you change the style of handling promises from async/ await to a standard <promise>.then() I was able to get the tests to pass.

here's an example component with an example test:

  <ul>
    <li 
      v-for="notification in notifications" 
      :key="notification.id"
    >
      {{notification.body}}
    </li>
  </ul>
</template>
<script>
import axios from 'axios';

export default {
  data(){
    return {
      notifications: []
    }
  },
  methods:{
    getNotifications(){
      axios.get('/notifications.json')
        .then(response =>  this.notifications = response.data.data)
    }
  },
  mounted(){
    this.getNotifications();
  }
}
</script>
import AppNotifications from '../AppNotifications';

jest.mock('axios', () => {
  return {
    get: () => Promise.resolve({
      data: {
        "data": [{
            "id": 1,
            "body": "first notification",
            "read": "true"
          },
          {
            "id": 2,
            "body": "second notification",
            "read": "false"
          }
        ]
      }
    })
  }
})

describe('AppNotification', () => {
  it('renders a list of notifications', async() => {
    let wrapper = mount(AppNotifications)
    await wrapper.vm.$nextTick(() => {    
      let items = wrapper.findAll('li');
      expect(items.at(0).text()).toContain('first notification')
      expect(items.at(1).text()).toContain('second notification')
    });
  });
})```