Vue TDD 예 3회: 어셈블리 추출

70369 단어 jestvuetutorialtdd
에서 vuex 저장소를 todo 구성 요소에 추가합니다.
이 집중에서, 우리는 폼을 추출하고 구성 요소를 표시함으로써 재구성을 완성할 것이다.
우리는 이전 강좌가 끝난 곳부터 시작할 것이다.만약 네가 몇 회 전에 관심을 가지지 않았다면, 나는 네가 이렇게 하는 것을 건의한다.
너는 github에서 시작하는 코드를 찾을 수 있다.

단계 1: "업무 추가" 양식 추출


구성 요소를 추출할 때 모든 코드를 새 구성 요소로 복사한 다음 테스트를 작성하거나 복사하기 쉽습니다.
그러나 이런 방법은 테스트를 실현할 수 있을 뿐만 아니라, 테스트 구동이 아닐 것이다.이번 방송에서 우리가 진행하고 있는 구체적인 재구성에 대해 우리는 테스트 구성 요소와 vuexstore의 상호작용을 필요로 한다. 이것은 우리가 아직 테스트에서 소개하지 않은 새로운 상호작용이다.우리는 먼저 테스트를 복제하고 수정할 것이며, 완성된 후에야 코드를 복제할 수 있다.
AddTodo 양식의 경우 먼저 구성 요소를 작성하도록 하는 테스트를 만듭니다.
// tests/unit/components/TodoInput.spec.js
import TodoInput from '@/components/TodoInput'
import { shallowMount } from '@vue/test-utils'

describe('The todo input component', function () {
  it('can be mounted', () => {
    const wrapper = shallowMount(TodoInput)
    expect(wrapper.exists()).toBe(true)
  })
})
구성 요소를 생성합니다.
// src/components/TodoInput.vue
<template>
  <div></div>
</template>

<script>
export default {
  name: 'TodoInput'
}
</script>
이제 우리는 조립품이 생겨서 일 처리 항목을 좀 봅시다.spec.js "어떤 테스트를 복사할 수 있습니까?나는 세 명의 가능한 후보를 보았다.
    it('allows for adding one todo item', async () => {
      await addTodo('My first todo item')
      expect(elementText('todos')).toContain('My first todo item')
    })
    it('allows for more than one todo item to be added', async () => {
      await addTodo('My first todo item')
      await addTodo('My second todo item')
      expect(elementText('todos')).toContain('My first todo item')
      expect(elementText('todos')).toContain('My second todo item')
    })
    it('empties the input field when todo has been added', async () => {
      await addTodo('This is not important')
      expect(wrapper.find('[data-testid="todo-input"]').element.value).toEqual('')
    })
앞의 두 테스트에서 우리는 보여준 html 검사를 사용하여 todo를 추가한 결과를 사용했다.vuex 메모리와의 상호작용을 테스트하기 위해서 이 테스트를 다시 작성해야 합니다.
마지막 테스트는 그대로 복제할 수 있습니다.
// tests/unit/components/TodoInput.spec.js
import TodoInput from '@/components/TodoInput'
import { shallowMount } from '@vue/test-utils'

describe('The todo input component', function () {
  let wrapper

  async function addTodo (todoText) {
    wrapper.find('[data-testid="todo-input"]').setValue(todoText)
    await wrapper.find('[data-testid="todo-submit"]').trigger('click')
  }

  it('can be mounted', () => {
    wrapper = shallowMount(TodoInput)
    expect(wrapper.exists()).toBe(true)
  })
  it('empties the input field when todo has been added', async () => {
    wrapper = shallowMount(TodoInput)
    await addTodo('This is not important')
    expect(wrapper.find('[data-testid="todo-input"]').element.value).toEqual('')
  })
})
테스트에 필요한 코드만 복제하여 테스트를 수정합니다.
// src/components/TodoInput.vue
<template>
  <div>
    <input
      type="text"
      data-testid="todo-input"
      placeholder="Add todo item..."
      class="border border-gray-300 p-1 text-blue-700"
      v-model="newTodo">
    <button
        class="px-3 py-1 text-white bg-blue-500 mb-4"
        data-testid="todo-submit"
        @click.prevent="addTodo">Add
    </button>
  </div>
</template>

<script>
export default {
  name: 'TodoInput',

  data () {
    return {
      newTodo: ''
    }
  },

  methods: {
    addTodo () {
      // this.$store.commit('ADD_TODO', this.newTodo)
      this.newTodo = ''
    }
  }
}
</script>
나는 테스트에서 우리는 아직 상점을 정의하지 않았기 때문에 상점에 대한 호출을 주석해 버렸다.그 외에, 우리는 이 줄의 주석을 취소하도록 하는 테스트가 필요하다.
다른 테스트를 복제하고 수정하기 전에 원시 테스트처럼 저장소를 추가해야 하지만, 지금은 돌연변이 가상 저장소가 하나밖에 없습니다. ADD_TODO우리는 jest mock function를 사용하여 이 변이를 실현했기 때문에 우리는 이 함수에 대한 호출을 감시할 수 있다.
// tests/unit/components/TodoInput.spec.js
import TodoInput from '@/components/TodoInput'
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Vuex from 'vuex'

const localVue = createLocalVue()
localVue.use(Vuex)
let store

describe('The todo input component', function () {
  let wrapper

  const mutations = {
    ADD_TODO: jest.fn()
  }

  beforeEach(() => {
    store = new Vuex.Store({
      mutations
    })
    wrapper = shallowMount(TodoInput, {
      localVue,
      store
    })
  })

  async function addTodo (todoText) {
    wrapper.find('[data-testid="todo-input"]').setValue(todoText)
    await wrapper.find('[data-testid="todo-submit"]').trigger('click')
  }

  it('can be mounted', () => {
    expect(wrapper.exists()).toBe(true)
  })
  it('empties the input field when todo has been added', async () => {
    await addTodo('This is not important')
    expect(wrapper.find('[data-testid="todo-input"]').element.value).toEqual('')
  })
})
현재, 우리는 아날로그 저장소를 만들어서 포장기를 만드는 데 사용하고, 이 두 테스트가 여전히 통과되었는지 확인합니다.나머지 테스트는 jest spy의 인자가 정확한지 확인하기 위해 복사하고 다시 쓸 수 있습니다.
// tests/unit/components/TodoInput.spec.js
  ...
  const mutations = {
    ADD_TODO: jest.fn()
  }
  ...

  it('allows for adding one todo item', async () => {
    await addTodo('My first todo item')
    // Note the first param is an empty object. That's the state the commit will be called with.
    // We didn't initialize any state, which causes the state to be an empty object.
    expect(mutations.ADD_TODO).toHaveBeenCalledWith({}, 'My first todo item')
  })
  it('allows for more than one todo item to be added', async () => {
    await addTodo('My first todo item')
    await addTodo('My second todo item')
    expect(mutations.ADD_TODO).toHaveBeenCalledTimes(2)
    // Note the first param is an empty object. That's the state the commit will be called with.
    // We didn't initialize any state, which causes the state to be an empty object.
    expect(mutations.ADD_TODO).toHaveBeenCalledWith({}, 'My first todo item')
    expect(mutations.ADD_TODO).toHaveBeenCalledWith({}, 'My second todo item')
  })
이러한 테스트를 통과하려면 스토리지를 호출하는 구성 요소의 행에 대한 설명을 취소합니다.
// src/components/TodoInput.vue
  methods: {
    addTodo () {
      this.$store.commit('ADD_TODO', this.newTodo)
      this.newTodo = ''
    }
  }

또 하나의 테스트가 통과되었으나 마지막 테스트가 실패하여 다음 메시지가 표시됩니다.
Error: expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 2
Received number of calls: 4
제출 함수는 두 번이 아니라 네 번 호출되었다.왜냐하면 우리는 테스트 사이에서 모크 함수를 제거하지 않았기 때문에 이 함수는 모든 호출을 누적할 것이다.우리는 beforeEach 함수의 모든 시뮬레이션을 제거해서 이 문제를 해결할 수 있다.
// tests/unit/components/TodoInput.spec.js
  ...
  beforeEach(() => {
    jest.clearAllMocks()
    store = new Vuex.Store({
      mutations
    })
    ...
  })
  ...
현재 모든 테스트가 통과되었다.첫 번째 테스트 (마운트할 수 있음) 를 삭제해서 테스트를 정리합시다. 이미 유행이 지났기 때문입니다.더 읽을 수 있도록 commit spy가 호출되었는지 확인하는 함수도 추출할 수 있습니다.이제 전체 테스트 파일은 다음과 같습니다.
// tests/unit/components/TodoInput.spec.js
import TodoInput from '@/components/TodoInput'
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Vuex from 'vuex'

const localVue = createLocalVue()
localVue.use(Vuex)
let store

describe('The todo input component', function () {
  let wrapper

  const mutations = {
    ADD_TODO: jest.fn()
  }

  beforeEach(() => {
    jest.clearAllMocks()
    store = new Vuex.Store({
      mutations
    })
    wrapper = shallowMount(TodoInput, {
      localVue,
      store
    })
  })

  async function addTodo (todoText) {
    wrapper.find('[data-testid="todo-input"]').setValue(todoText)
    await wrapper.find('[data-testid="todo-submit"]').trigger('click')
  }

  function expectMutationToHaveBeenCalledWith (item) {
    // Note the first param is an empty object. That's the state the commit will be called with.
    // We didn't initialize any state, which causes the state to be an empty object.
    expect(mutations.ADD_TODO).toHaveBeenCalledWith({}, item)
  }

  it('empties the input field when todo has been added', async () => {
    await addTodo('This is not important')
    expect(wrapper.find('[data-testid="todo-input"]').element.value).toEqual('')
  })
  it('allows for adding one todo item', async () => {
    await addTodo('My first todo item')
    expectMutationToHaveBeenCalledWith('My first todo item')
  })

  it('allows for more than one todo item to be added', async () => {
    await addTodo('My first todo item')
    await addTodo('My second todo item')
    expect(mutations.ADD_TODO).toHaveBeenCalledTimes(2)
    expectMutationToHaveBeenCalledWith('My first todo item')
    expectMutationToHaveBeenCalledWith('My second todo item')
  })
})
입력된 구성 요소가 완성되었습니다.이 코드는 github에서 찾을 수 있습니다.

2단계: 업무 목록 추출


양식과 동일한 방법으로 대기사항 목록 구성 요소를 만듭니다.
  • 구성 요소를 만들도록 강요
  • 원본 구성 요소
  • 에서 테스트를 복제하려고 합니다.
  • 테스트에 스토리지 추가
  • 나는 더 이상 강제로 구성 요소를 만드는 것을 토론하지 않을 것이다.테스트 파일에서 그것을 첫 번째 테스트로 찾을 수 있습니다. (코드에 남겨 두겠습니다.)
    원본 구성 요소에는 목록 구성 요소의 기능을 포함하는 두 가지 테스트가 있습니다.
        it('displays the items in the order they are entered', async () => {
          await addTodo('First')
          await addTodo('Second')
          expect(elementText('todo-0')).toMatch('First')
          expect(elementText('todo-1')).toMatch('Second')
        })
        it('items can be marked as done by clicking an element before the item.', async () => {
          function itemIsDone (itemId) {
            return wrapper.find(`[data-testid="todo-${itemId}"]`).attributes('data-done') === 'true'
          }
    
          await addTodo('First')
          await addTodo('Second')
    
          expect(itemIsDone(0)).toBe(false)
          await wrapper.find('[data-testid="todo-0-toggle"]').trigger('click')
          expect(itemIsDone(0)).toBe(true)
        })
    
    새로운 구성 요소에서 유용하게 사용할 수 있도록 이러한 테스트를 대폭 변경해야 합니다. 테스트를 수행해야 하기 때문입니다.
  • 에 표시된 업무 사항은 vuex 상점에서 검색됩니다.
  • 스토리지 돌연변이를 사용하여 프로젝트를 전환합니다.
  • 우리는 시뮬레이션 상점을 확장해서 이 두 가지 상호작용을 시뮬레이션할 것이다.프로젝트 목록부터 시작하겠습니다.
    // tests/unit/components/TodoList.spec.js
    import { createLocalVue, shallowMount } from '@vue/test-utils'
    import TodoList from '@/components/TodoList'
    import Vuex from 'vuex'
    
    const localVue = createLocalVue()
    localVue.use(Vuex)
    let store
    
    describe('The TodoList component', function () {
      let wrapper
    
      const getters = {
        todos: jest.fn(() => [{
          description: 'First',
          done: false
        }, {
          description: 'Second',
          done: false
        }])
      }
    
      beforeEach(() => {
        store = new Vuex.Store({
          getters
        })
        wrapper = shallowMount(TodoList, {
          localVue,
          store
        })
      })
    
      it('can be mounted', () => {
        expect(wrapper.exists()).toBe(true)
      })
    })
    
    우리는 store todos getter를 시뮬레이션하기 위해jest mock 함수를 사용합니다.이제 검사 항목의 순서를 복제하고 수정하는 테스트를 준비합니다.
    // tests/unit/components/TodoList.spec.js
        ...
        function elementText (testId) {
          return wrapper.find(`[data-testid="${testId}"]`).text()
        }
        ...
        it('displays the items in the order they are present in the store', async () => {
          expect(elementText('todo-0')).toMatch('First')
          expect(elementText('todo-1')).toMatch('Second')
        })
        ...
    
    물론 그것은 실패했다.테스트를 통과할 수 있도록 원본 구성 요소에서 충분한 코드를 복사합니다.
    // src/components/TodoList.vue
    <template>
      <ul data-testid="todos" class="text-left">
        <li
            v-for="(todo, todoKey) of todos"
            :data-testid="`todo-${todoKey}`"
            :data-done="todo.done"
            :key="todoKey"
            class="block mb-3"
            :class="todo.done ? 'done' : ''"
        >
            <span
                :data-testid="`todo-${todoKey}-toggle`"
                @click.prevent="toggle(todo)"
                class="checkbox"
                :class="todo.done ? 'done' : ''"
            > {{ todo.done ? "Done" : "Mark done" }}</span>
          {{ todo.description }}
        </li>
      </ul>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    
    export default {
      name: 'TodoList',
    
      computed: {
        ...mapGetters(['todos'])
      }
    }
    </script>
    
    
    그리고 지나갔어요.우리가 정말로 상점을 사용하고 있는지 확인하기 위해서, Getter를 호출할 수 있도록 검사를 추가했습니다.
    // tests/unit/components/TodoList.spec.js
      beforeEach(() => {
        jest.clearAllMocks()
        ...
      })
      ...
      it('displays the items in the order they are present in the store', async () => {
        expect(getters.todos).toHaveBeenCalledTimes(1)
        expect(elementText('todo-0')).toMatch('First')
        expect(elementText('todo-1')).toMatch('Second')
      })
      ...
    
    이 테스트를 통과하기 위해서, 우리는 이전과 같이 모든 시뮬레이션을 제거해야 하기 때문에, 우리는 이 특정한 테스트의 호출만 계산해야 한다.
    유일하게 검사해야 할 것은 스위치다.todo가 '완성' 으로 설정되었을 때, 저장소에 돌연변이를 제출해야 합니다.우선, 우리는 이런 돌변을 위해 시뮬레이션 상점을 준비한다.
    // tests/unit/components/TodoList.spec.js
      ...
      const mutations = {
        TOGGLE_TODO: jest.fn()
      }
    
      beforeEach(() => {
        jest.clearAllMocks()
        store = new Vuex.Store({
          getters,
          mutations
        })
        ...
      })
      ...
    
    그런 다음 테스트를 만듭니다.
    // tests/unit/components/TodoList.spec.js
      it('items can be marked as done by clicking an element before the item.', async () => {
        await wrapper.find('[data-testid="todo-0-toggle"]').trigger('click')
        expect(mutations.TOGGLE_TODO).toHaveBeenCalledWith({}, {
          description: 'First',
          done: false
        })
      })
    
    
    이 테스트는 복제Todo.vue의 전환 방법을 통해 수행됩니다.
    // src/components/TodoList.vue
    import { mapGetters } from 'vuex'
    
    export default {
      name: 'TodoList',
    
      computed: {
        ...mapGetters(['todos'])
      },
    
      methods: {
        toggle (todo) {
          this.$store.commit('TOGGLE_TODO', todo)
        }
      }
    }
    
    이렇게 하면 TodoList 어셈블리가 완성됩니다.이 코드는 github에서 찾을 수 있습니다.

    3단계: 새 구성 요소 사용


    현재 우리는 새 구성 요소가 생겨서, 우리는 낡은 구성 요소를 다시 쓸 수 있기 때문에, 이 새 구성 요소를 사용한다.통합 테스트를 통해 아직 유효한지 확인합니다.
    <template>
      <div>
        <h2 class="mb-4">{{ title }}</h2>
        <TodoInput />
        <TodoList />
      </div>
    </template>
    
    <script>
    import TodoInput from '@/components/TodoInput'
    import TodoList from '@/components/TodoList'
    
    export default {
      name: 'Todo',
    
      components: {
        TodoInput,
        TodoList
      },
    
      props: {
        title: {
          type: String,
          required: true
        }
      }
    }
    </script>
    
    그런데 실패했어!어떻게 된 거야?걱정하지 마라, 이것은 예상한 것이다.테스트에서 우리는 shallowMount를 사용했지만 구성 요소를 추출했기 때문에 우리는 mount를 사용해야 한다. 이것은 하위 구성 요소를 나타낼 것이다.
    // tests/unit/components/Todo.spec.js
    import { mount, createLocalVue } from '@vue/test-utils'
    import Vuex from 'vuex'
    import Todo from '@/components/Todo'
    import { createStore } from '@/store'
    
    const localVue = createLocalVue()
    localVue.use(Vuex)
    let store
    
    describe('The Todo.vue component', () => {
      beforeEach(() => {
        store = createStore()
      })
      it('Displays the title when passed as a prop', () => {
        const wrapper = mount(Todo, {
          localVue,
          store,
          propsData: {
            title: 'A random title'
          }
        })
        expect(wrapper.text()).toMatch('A random title')
        const wrapper2 = mount(Todo, {
          localVue,
          store,
          propsData: {
            title: 'Another random one'
          }
        })
        expect(wrapper2.text()).toMatch('Another random one')
      })
      describe('adding todo items', () => {
        let wrapper
    
        beforeEach(() => {
          wrapper = mount(Todo, {
            localVue,
            store,
            propsData: {
              title: 'My list'
            }
          })
        })
    
        async function addTodo (todoText) {
          wrapper.find('[data-testid="todo-input"]').setValue(todoText)
          await wrapper.find('[data-testid="todo-submit"]').trigger('click')
        }
    
        function elementText (testId) {
          return wrapper.find(`[data-testid="${testId}"]`).text()
        }
    
        it('allows for adding one todo item', async () => {
          await addTodo('My first todo item')
          expect(elementText('todos')).toContain('My first todo item')
        })
        it('allows for more than one todo item to be added', async () => {
          await addTodo('My first todo item')
          await addTodo('My second todo item')
          expect(elementText('todos')).toContain('My first todo item')
          expect(elementText('todos')).toContain('My second todo item')
        })
        it('empties the input field when todo has been added', async () => {
          await addTodo('This is not important')
          expect(wrapper.find('[data-testid="todo-input"]').element.value).toEqual('')
        })
        it('displays the items in the order they are entered', async () => {
          await addTodo('First')
          await addTodo('Second')
          expect(elementText('todo-0')).toMatch('First')
          expect(elementText('todo-1')).toMatch('Second')
        })
        it('items can be marked as done by clicking an element before the item.', async () => {
          function itemIsDone (itemId) {
            return wrapper.find(`[data-testid="todo-${itemId}"]`).attributes('data-done') === 'true'
          }
    
          await addTodo('First')
          await addTodo('Second')
    
          expect(itemIsDone(0)).toBe(false)
          await wrapper.find('[data-testid="todo-0-toggle"]').trigger('click')
          expect(itemIsDone(0)).toBe(true)
        })
      })
    })
    
    
    이제 지나갔어, 우리는 끝났어!이 코드는 github에서 찾을 수 있습니다.

    결론


    이번 회에서는 일련의 Vue TDD를 하나의 예로 끝냈다.내가 이 시리즈를 만든 것은 Vue의 TDD에서 찾은 자원 중의 재구성 부분을 놓쳤기 때문이다.
    만약 당신이 더 많은 것을 알고 싶다면, 인터넷에는 더 많은 자원이 있다.나는 중학교 때부터 많은 것을 배웠다.
  • Learn TDD in Vue
  • Outside-In Frontend Development
  • 켄트벡
  • 의 책'테스트 드라이브 개발: 통과 예'

    좋은 웹페이지 즐겨찾기