store -> movie.js 파일 테스트

 

관련 공식문서

https://jestjs.io/docs/mock-function-api

 

Mock Functions · Jest

Mock functions are also known as "spies", because they let you spy on the behavior of a function that is called indirectly by some other code, rather than only testing the output. You can create a mock function with jest.fn(). If no implementation is given

jestjs.io

 

테스트를 진행할 코드

import axios from 'axios'
//  중복제거을 위해서 uniqBy를 사용
import _uniqBy from 'lodash/uniqBy'

const _defaultMessage = 'Search for the movie title!'

export default {
  // module!
  namespaced: true,
  //data!
  state: () => ({
    movies: [],
    message: _defaultMessage,
    loading: false,
    theMovie: {}
  }),
  // computed!
  getters: {
    // 실제 데이터를 계산해서 새로운 데이터 형식으로 반영할때 사용
    
  },
  // methods!
  // 변이: 관리하는 데이터를 변경시켜줄수 있다. 다른 메소드에서 변경할 수 없다.
  mutations: {
    // ['movies', 'message', 'loading']
    updateState(state, payload) {
      Object.keys(payload).forEach(key => {
        state[key] = payload[key]
        
      })
    },
    // assignMovies (state, Search){
    //   state.movies = Search
    // },
    // 화면 이동시 초기화 설정
    resetMovies(state) {
      state.movies = []
      state.message = _defaultMessage
      state.loading = false
    }
  },
  // 비동기로 동작한다.
  actions: {
    async searchMovies({state, commit}, payload) {

      // 영화검색을 동시에 동작하는 것을 방지
      if(state.loading) {
        return
      }

      commit('updateState', {
        message: '',
        loading: true
        // 검색된 imdbID 데이터의 중복을 제거
        // movies: _uniqBy(Search,'imdbID')
        // message: 'Hello world',
        // loading: true
      })
      try {
                // const { title, type, number, year } = payload
        // const OMDB_API_KEY = '7035c60c'
        // http -> https 로 변경
        //const res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`);
        const res = await _fetchMovie({
          // 전개연산자 사용
          ...payload,
          page: 1
        })
         console.log(res);
        const { Search, totalResults } = res.data
        commit('updateState', {
          // 검색된 imdbID 데이터의 중복을 제거
          movies: _uniqBy(Search,'imdbID')
          // message: 'Hello world',
          // loading: true
        })
        
        console.log(totalResults) // 261
        console.log(typeof totalResults) // string

        const total = parseInt(totalResults, 10)
        const pageLength = Math.ceil(total / 10)

        // 추가 요청!
        if (pageLength > 1) {
          for (let page = 2; page <= pageLength; page += 1) {
            if (page > payload.number / 10) {
              // 반복문 종료
              // 사용자가 지정한 갯수만큼 보여주도록 설정
              break;
            }
            //const res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`);
            const res = await _fetchMovie({
              ...payload,
              page: page
            })
            const {Search} = res.data
            commit('updateState', {
              // _uniqBy 를 이용해서 중복 제거
              movies: [
                ...state.movies,
                ..._uniqBy(Search, 'imdbID')
              ]
            })
          }
        }
      } catch ({message}) {
        commit('updateState', {
          // 초기화
          movies: [],
          // 메세지 출력
          message: message
        })
      } finally {
        commit('updateState', {
          loading: false
        })
      }
    },
    async searchMovieWithId({state, commit}, payload) {
      if(state.loading) return 

      commit('updateState', {
        theMovie: {},
        loading: true
      })
      //const {id} = payload
      try {
        const res = await _fetchMovie(payload)
        commit('updateState',{
          theMovie: res.data
          // id: id
        })
        console.log(res)
      } catch(error) {
        commit('updateState', {
          theMovie: {}
        })
      } finally {
        commit('updateState', {
          loading: false
        })
      }
    }
  }
}

async function _fetchMovie(payload) {
  // 서버리스 함수로 처리
  return await axios.post('/.netlify/functions/movie', payload)

  // const { title, type, year, page, id } = payload;
  // const OMDB_API_KEY = '7035c60c';
  // const url = id 
  //   ? `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${id}` 
  //   : `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`;
  // // const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}`;

  // return new Promise((resolve, reject) => {
  //   axios.get(url)
  //     .then((res) => {
  //       // console.log(res)
  //       if (res.data.Error) {
  //         reject(res.data.Error)
  //       }
  //       resolve(res)
  //     })
  //     .catch((err) => {
  //       reject(err.message)
  //     })
  // })
}

 

테스트 코드

import movieStore from '~/store/movie.js'
import _cloneDeep from 'lodash/cloneDeep'
import axios from 'axios'

describe('store/movie.js', () => {
  let store

  beforeEach(() => {
    // 원본 훼손을 방지하기 위해 복사 기능 이용
    store = _cloneDeep(movieStore)
    store.state = store.state()
    // this.$store.state.movies
    store.commit = (name, payload) => {
      store.mutations[name](store.state, payload)
      
    }
    
     store.dispatch = (name, payload) => {
       const context = {
         state: store.state,
         commit: store.commit,
         dispatch: store.dispatch
       }
       return store.actions[name](context, payload)
     }
    
  })

  test('영화 데이터를 초기화합니다.', () => {
    store.commit('updateState', {
      movies: [{imdbId: '1'}],
      message: 'Hello world',
      loading: true
    })
    store.commit('resetMovies')    
    expect(store.state.movies).toEqual([])
    expect(store.state.message).toBe('Search for the movie title!')
    expect(store.state.loading).toBe(false)
    //store.mutations.updateState(store.state, {})
    //store.dispatch('searchMovies', {})
  })

  test('영화 목록을 잘 가져온 경우 데이터를 확인합니다.', async () => {
    const res = {
      data: {
        totalResults: '1',
        Search: [
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          }
        ]
      }
    }
    // 모의함수 및 가짜 데이터 설정
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovies')
    expect(store.state.movies).toEqual(res.data.Search)
  })

  test('영화 목록을 가져오지 못한 경우 에러 메시지를 확인합니다.', async () => {
    const errorMessage = 'Network Error'
    axios.post = jest.fn().mockRejectedValue(new Error(errorMessage))
    await store.dispatch('searchMovies')
    expect(store.state.message).toBe(errorMessage)
  })

  test('영화 아이템이 중복된 경우 고유하게 처리합니다.', async () => {
    const res = {
      data: {
        totalResults: '1',
        Search: [
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          },
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          },
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          }
        ]
      }
    }
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovies')
    expect(store.state.movies.length).toBe(1)
  })

  test('단일 영화의 상세 정보를 잘 가져온 경우 데이터를 확인합니다.', async () => {
    const res = {
      data: {
        imdbID: '1',
        Title: 'Frozen',
        Poster: 'frozen.jpg',
        Year: '2021'
      }
    }
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovieWithId')
    expect(store.state.theMovie).toEqual(res.data)
  })
})

 

공식 문서

Jest CLI Options · Jest (jestjs.io)

 

Jest CLI Options · Jest

The jest command line runner has a number of useful options. You can run jest --help to view all available options. Many of the options shown below can also be used together to run tests exactly the way you want. Every one of Jest's Configuration options c

jestjs.io

 

package.json 파일 설정 수정

명령어 추가

"test:unit:silent": "jest --watchAll --silent",

 

 

'프론트엔드 > 단위 테스트' 카테고리의 다른 글

movie 스토어 테스트  (0) 2022.02.02
Movie 컴포넌트 테스트  (0) 2022.02.01
Search 컴포넌트 테스트  (0) 2022.01.31
Header 컴포넌트 테스트, url 테스트  (0) 2022.01.27
mount vs shallowMount  (0) 2022.01.24

Movie.vue 테스트

영화정보 id를 제대로 가져오는지 확인

 

테스트 코드

import {shallowMount} from '@vue/test-utils'
import store from '~/store'
import router from '~/routes'
import loadImage from '~/plugins/loadImage'
import Movie from '~/routes/Movie'

describe('routes/Movie.vue', () => {
  let wrapper

  beforeEach(async () => {
    window.scrollTo = jest.fn()
    router.push('/movie/tt1234567')
    await router.isReady()
    wrapper = shallowMount(Movie, {
      global: {
        plugins: [
          store,
          router,
          loadImage
        ]
      }
    })
  })

  test('최초 접속한 URL의 파라미터를 확인합니다.', () => {
    expect(wrapper.vm.$route.params.id).toBe('tt1234567?')
  })
})

 

 

 이미지크기 변환 테스트

  test('지정한 이미지 크기로 URL을 변경합니다.', () => {
    const url = 'https://google.com/sample_image_SX300.jpg'
    expect(wrapper.vm.requestDiffSizeImage(url)).toContain('SX700?.jpg')
  })

 

데이터가 존재하지않거나, N/A일경우 빈문자열로 호출하는지 테스트

 test('정상적인 이미지 주소가 아닌 경우 빈 문자열을 반환합니다.', () => {
    expect(wrapper.vm.requestDiffSizeImage()).toBe('?')
    expect(wrapper.vm.requestDiffSizeImage('N/A')).toBe('?')
  })

연도별 조회기능 테스트

기존기능 코드 (Search.vue)

test 코드 작성

Search.test.js

import {shallowMount} from '@vue/test-utils'
import Search from '~/components/Search'

describe('components/Search.vue', () => {
  let wrapper

  beforeEach(() => {
    wrapper = shallowMount(Search)
  })

  test('선택 가능한 연도의 개수가 일치합니다.', () => {
    const year = wrapper.vm.filters.find((filter) => {
      return filter.name === 'year'
    })
    const yearLaength = new Date().getFullYear() - 1985 + 1
    expect(year.items.length).toBe(123)
  })
})

최대한 테스트를 진행할때 외부에 자원을 연결하지 않은 상태에서 진행하는 것을 권장한다.

각각의 테스트는 고유한 테스트 환경을 갖추어야 한다.

따라서 테스트 안에서 매번 마운트 해주는 방법이 있다.

또는 beforEach를 이용하여 테스트 전에 마운트를 다시 한번 해주는 방법이 있다.

아래 예제에서는 beforEach를 이용하는 방법을 사용하였다.

 

페이지 이동을 위한 공식문서

Testing Vue Router | Vue Test Utils for Vue 3 (2.0.0-rc.18) (vuejs.org)

 

Testing Vue Router | Vue Test Utils for Vue 3 (2.0.0-rc.18)

Testing Vue Router This article will present two ways to test an application using Vue Router: Using the real Vue Router, which is more production like but also may lead to complexity when testing larger applicationsUsing a mocked router, allowing for more

next.vue-test-utils.vuejs.org

코드 작성 Header.test.js

import {shallowMount} from '@vue/test-utils'
import router from '~/routes/index.js'
import store from '~/store'
import Header from '~/components/Header'

describe('components/Header.vue', () => {

 let wrapper
  // 매번 새로운 테스트 환경에서 실행되도록 설정
  beforeEach(() => {
    // 페이지 이동
    router.push('/movie/tt1234567')
    //await router.

    wrapper = shallowMount(Header, {
      global: {
        plugins: [
          router,
          store
        ]
      }
    })
  })
  

  test('경로 정규표현식이 없는 경우 일치하지 않습니다.', () => {
    
    const regExp = undefined
    expect(wrapper.vm.isMatch(regExp)).toBe(false)
  })

  test('경로 정규표현식과 일치해야 합니다.', () =>{
    const regExp = /^\/movie/
    expect(wrapper.vm.isMatch(regExp)).toBe(123)
  })
})

 

Not implemented: window.scrollTo: window.scrollTo가 준비되지 않았다는 메세지

원인 => index.js

 

코드 수정 Header.test.js

// jest를 이용하여 스크롤 동작하는 것처럼 설정 -> 모의 함수설정
import {shallowMount} from '@vue/test-utils'
import router from '~/routes/index.js'
import store from '~/store'
import Header from '~/components/Header.vue'

describe('components/Header.vue', () => {

 let wrapper
  // 매번 새로운 테스트 환경에서 실행되도록 설정
  beforeEach(async () => {
    // jest를 이용하여 스크롤 동작하는 것처럼 설정
    window.scrollTo = jest.fn()
    // 페이지 이동
    router.push('/movie/tt1234567')
    await router.isReady()

    wrapper = shallowMount(Header, {
      global: {
        plugins: [
          router,
          store
        ]
      }
    })
  })
  

  test('경로 정규표현식이 없는 경우 일치하지 않습니다.', () => {
    
    const regExp = undefined
    expect(wrapper.vm.isMatch(regExp)).toBe(false)
  })

  test('경로 정규표현식과 일치해야 합니다.', () =>{
    const regExp = /^\/movie/
    expect(wrapper.vm.isMatch(regExp)).toBe(123)
  })
})

 

정상적으로 true 값이 나오는것을 확인

 

 

'프론트엔드 > 단위 테스트' 카테고리의 다른 글

Movie 컴포넌트 테스트  (0) 2022.02.01
Search 컴포넌트 테스트  (0) 2022.01.31
mount vs shallowMount  (0) 2022.01.24
VTU API 공식 문서 사이트  (0) 2022.01.23
VTU 첫 테스트 및 에러 해결(버전 문제)  (0) 2022.01.21

단위 테스트는 최대한 가볍게 진행해야 하므로 얕은 마운트를 의미하는 shallowmount를 사용하기를 권장한다.

 

Parent.vue

<template>
  <h1>Parent</h1>
  <Child msg="Happy" />
</template>

<script>
import Child from './Child'
export default {
  components: {
    Child  
  }
}
</script>

Child.vue

<template>
  <div>Child: {{ msg }}</div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ''
    }
  }
}
</script>

Parent.test.js

import {mount} from '@vue/test-utils'
import {shallowMount} from '@vue/test-utils'
import Parent from './Parent'

test('Mount', () => {
  const wrapper = mount(Parent)
  expect(wrapper.html()).toBe('')
})

test('shallowMount', () => {
  const wrapper = shallowMount(Parent)
  expect(wrapper.html()).toBe('')
})

 

 

shallowMount를 사용한 결과는 child 태그 뒤에 stub가 붙어서 나온다.

mock, stub는 가짜, 모의를 의미한다.

 

mount

연결하는 특정 컴포넌트에 하위에 연결된 컴포넌트또한 랜더링 실행,

테스트가 무거워지는 문제점이 생긴다.

 

shallowmount

연결하는 특정 컴포넌트만 랜더링 실행,

하위에 컴포넌트는 stub를 붙여서 가짜로 연결이 된것처럼 보여준다.

 

+ Recent posts