티스토리 뷰

728x90

 

코드가 변경되면 자동으로 테스트가 수행되도록 스크립트를 하나 추가했습니다.

 

// package.json

"scripts": {
    "test": "jest",
    "test:watch": "jest --watchAll"
  },

 

그리고 layouts 폴더도 테스트 커버리지에 포함시켰습니다.

 

// jest.config.js

  collectCoverageFrom: [
    '<rootDir>/components/**/*.vue',
    '<rootDir>/pages/**/*.vue',
    '<rootDir>/layouts/**/*.vue'
  ]

 

테스트 코드를 작성하면서 만났던 이슈는 체크 박스의 checked 값을 확인할 수 없다는 사실이었습니다.

그래서 체크 박스에 vlaue 값을 추가했고 그 값으로 렌더링이 정상적으로 되었는지 체크했습니다.

 

// TodoList.vue

// before
<input :checked="todo.completed" type="checkbox" @change="toggleTodo(todo.id)">

// after
<input :value="todo.completed" :checked="todo.completed" type="checkbox" @change="toggleTodo(todo.id)">

 

테스트 코드도 코드이기 때문에 중복되는 코드는 함수로 분리(getStore, getWrapper)해서 재사용했습니다.

BDD 방식으로 단위 테스트를 작성했습니다. 보기 편하게 하려고 given-when-then 주석을 달았습니다.

 

import { createLocalVue, shallowMount, RouterLinkStub } from '@vue/test-utils'
import Vuex from 'vuex'

import { getters } from '~/store'
import TodoList from '~/components/TodoList'
const localVue = createLocalVue()
localVue.use(Vuex)

describe('TodoList Component', () => {
  const getStore = (state, actions) => {
    return new Vuex.Store({
      state,
      getters,
      actions
    })
  }

  const getWrapper = (store) => {
    return shallowMount(TodoList, {
      store,
      localVue,
      stubs: {
        NuxtLink: RouterLinkStub
      }
    })
  }

  test('does not render li when store state todos is empty', () => {
    // given
    const state = {
      todos: [],
      visibility: 'all',
      selectedTodo: null
    }
    const store = getStore(state)

    // when
    const wrapper = getWrapper(store)

    // then
    expect(wrapper.findAll('li').length).toBe(0)
  })

  test('render todo when store state todos is not empty', () => {
    // given
    const todo = {
      id: 'todo-1',
      text: 'aadfjadfasdfasdfasdf',
      completed: false
    }
    const state = {
      todos: [todo],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state)

    // when
    const wrapper = getWrapper(store)

    // then
    expect(wrapper.findAll('li').length).toBe(1)
    expect(wrapper.find('li').find('span').text()).toBe(todo.text)
  })

  test('completed checkbox\'s value false when store todo completed value is false.', () => {
    // given
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: false
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state)

    // when
    const wrapper = getWrapper(store)

    // then
    expect(wrapper.find('input[type="checkbox"]').attributes('value')).toBe('false')
  })

  test('completed checkbox\'s value true when store todo completed value is true.', () => {
    // given
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: true
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state)

    // when
    const wrapper = getWrapper(store)

    // then
    expect(wrapper.find('input[type="checkbox"]').attributes('value')).toBe('true')
  })

  test('call store action `toggleTodo` when completed checkbox is clicked', () => {
    // given
    const toggleTodo = jest.fn()
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: false
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state, {
      toggleTodo
    })
    const wrapper = getWrapper(store)

    // when
    wrapper.find('input[type="checkbox"]').trigger('click')

    // then
    expect(toggleTodo).toHaveBeenCalledTimes(1)
  })

  test('call store action `setTodo` when todo is clicked', () => {
    // given
    const setTodo = jest.fn()
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: false
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state, {
      setTodo
    })
    const wrapper = getWrapper(store)

    // when
    wrapper.find('span').trigger('click')

    // then
    expect(setTodo).toHaveBeenCalledTimes(1)
  })

  test('call store action `setTodo` when edit button is clicked ', () => {
    // given
    const setTodo = jest.fn()
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: false
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state, {
      setTodo
    })
    const wrapper = getWrapper(store)

    // when
    wrapper.findAll('button').at(1).trigger('click')

    // then
    expect(setTodo).toHaveBeenCalledTimes(1)
  })

  test('call store action `removeTodo` when delete button is clicked', () => {
    // given
    const removeTodo = jest.fn()
    const state = {
      todos: [{
        id: 'todo-1',
        text: 'aadfjadfasdfasdfasdf',
        completed: false
      }],
      visibility: 'all',
      selectedTodo: null
    }

    const store = getStore(state, {
      removeTodo
    })
    const wrapper = getWrapper(store)

    // when
    wrapper.findAll('button').at(0).trigger('click')

    // then
    expect(removeTodo).toHaveBeenCalledTimes(1)
  })
})

 

전체 코드는 github에 있습니다.

 

 

# 추가

# 2020. 5. 8

trigger 후 render 결과를 확인하려는 경우 이슈가 있습니다.

공식문서async/await 방법을 아래 처럼 가이드 하고 있으나 beta.33에서는 동작하지 않습니다.

it('button click should increment the count text', async () => {
  expect(wrapper.text()).toContain('0')
  const button = wrapper.find('button')
  await button.trigger('click')
  expect(wrapper.text()).toContain('1')
})

 

https://github.com/vuejs/vue-test-utils/issues/1515 에서 추가로 수정되었는데 아직 release가 안됐습니다.

당장 해결 방법은 async/await를 사용하지 않고 promise 패턴으로 사용하는 방식으로 아래처럼 가능합니다.

 

it('button click should increment the count text', () => {
  expect(wrapper.text()).toContain('0')
  const button = wrapper.find('button')
  button.trigger('click').then(() => {
    expect(wrapper.text()).toContain('1')
  })
})

 

 

# 참고

728x90
댓글