Jest와 VTU로 Vue 테스트하기(2)

tkppp·2022년 1월 14일
0

실제 테스트 코드

// RegisterPage.spec.js
import { mount } from '@vue/test-utils';
import RegisterPage from '@/views/RegisterPage';

jest.mock('@/services/registration');

describe('RegisterPage.vue', () => {
  let wrapper, fieldUsername, fieldPassword, fieldEmailAddress, buttonSubmit;
  const mockRouter = {
    push: jest.fn(),
  };

  beforeEach(() => {
    wrapper = mount(RegisterPage, {
      global: {
        mocks: {
          $router: mockRouter,
        },
        /* or
        plugins: [router]
        */
      },
    });
    fieldUsername = wrapper.find('#username');
    fieldEmailAddress = wrapper.find('#emailAddress');
    fieldPassword = wrapper.find('#password');
    buttonSubmit = wrapper.find('form button[type="submit"]');
  });

  afterAll(() => {
    jest.restoreAllMocks();
  });

  it('마운트 시, 컨텐츠가 올바르게 렌더링 되어야 한다', () => {
    expect(wrapper.find('.logo').attributes('src')).toEqual(
      '/static/images/logo.png'
    );

    expect(wrapper.find('.tagline').text()).toEqual(
      'Open source task management tool'
    );

    expect(fieldUsername.element.value).toEqual('');
    expect(fieldEmailAddress.element.value).toEqual('');
    expect(fieldPassword.element.value).toEqual('');
    expect(buttonSubmit.text()).toEqual('Create account');
  });

  it('데이터 모델의 초기값이 빈 문자열이어야 한다.', () => {
    expect(wrapper.vm.form.username).toEqual('');
    expect(wrapper.vm.form.emailAddress).toEqual('');
    expect(wrapper.vm.form.password).toEqual('');
  });

  it('폼의 입력과 데이터 모델의 바인딩이 정상적으로 되야 한다.', async () => {
    const form = wrapper.vm.form;
    const username = 'tkppp';
    const emailAddress = 'gowldla0423@naver.com';
    const password = 'password1';

    await fieldUsername.setValue(username);
    await fieldEmailAddress.setValue(emailAddress);
    await fieldPassword.setValue(password);

    expect(fieldUsername.element.value).toEqual(form.username);
    expect(fieldEmailAddress.element.value).toEqual(form.emailAddress);
    expect(fieldPassword.element.value).toEqual(form.password);
  });

  it('폼 제출 이벤트 핸들러에 "submitForm"이 등록되어야 한다.', async () => {
    const spyFn = jest.spyOn(wrapper.vm, 'submitForm')

    await buttonSubmit.trigger('submit');
    expect(spyFn).toBeCalled();
  });

  it('새로운 유저의 경우, 회원가입이 성공해야 한다', async () => {
    wrapper.vm.form.username = 'tkppp';
    wrapper.vm.form.emailAddress = 'gowldla0423@naver.com';
    wrapper.vm.form.password = 'password1';
    await buttonSubmit.trigger('submit')
    expect(mockRouter.push).toHaveBeenCalledWith({ name: 'LoginPage' });
    
  });

  it('등록된 유저의 경우, 회원가입이 실패하고 실패 메세지를 표시해야 한다.', async () => {
    wrapper.vm.form.emailAddress = 'exist@local';
    expect(wrapper.find('.failed').isVisible()).toBe(false);
    await buttonSubmit.trigger('submit')
    expect(wrapper.find('.failed').isVisible()).toBe(true);
  });
});

변경점

테스트 : 폼의 입력과 데이터 모델의 바인딩이 정상적으로 되야 한다.

// 기존 책 코드
it('test name', () => {
    const username = 'tkppp';
    const emailAddress = 'gowldla0423@naver.com';
    const password = 'password1';

    wrapper.vm.form.username = username
    wrapper.vm.form.emailAddress = emailAddress
    wrapper.vm.form.password = password
    expect(fieldUsername.element.value).toEqual(username);
    expect(fieldEmailAddress.element.value).toEqual(emailAddress);
    expect(fieldPassword.element.value).toEqual(password);
})

기존 책의 테스트는 input의 value가 업데이트 되지 않아 테스트가 실패한다. wrapper 객체의 vm을 통한 data 변경은 재렌더링이 되지 않는 것으로 보인다. 따라서 비동기 함수 setValue()를 통해 input 값을 변경하고 바인딩된 data가 제대로 변경되는지를 확인하는 방식으로 테스트를 변경하였다.

테스트 : 폼 제출 이벤트 핸들러에 "submitForm"이 등록되어야 한다.

// 기존 책 코드
it('test name', () => {
    const stub = jest.fn()
    wrapper.setMethods({submitform: stub})
    buttonSubmit.trigger('submit')
    expect(stub).tobeCalled()
})

setMethods() 함수는 deprecated 되었고 Vue3 테스트에서는 아예 사라졌다. 따라서 spyOn을 통해 wrapper.vm 객체의 submitForm() 함수를 감시하는 방식으로 테스트를 변경하였다.

테스트 : 새로운 유저의 경우, 회원가입이 성공해야 한다. & 등록된 유저의 경우, 회원가입이 실패하고 실패 메세지를 표시해야 한다.

// 기존 책 코드
it('새로운 유저의 경우, 회원가입이 성공해야 한다', () => {
    const stub = jest.fn()
    wrapper.vm.$router.push = stub
    wrapper.vm.form.username = 'tkppp';
    wrapper.vm.form.emailAddress = 'gowldla0423@naver.com';
    wrapper.vm.form.password = 'password1';
    wrapper.vm.sumitForm()
    wrapper.vm.$nextTick(() => {
    	expect(stub).toHaveCalledWith({name: 'LoginPage'})
    }
})
  
it('등록된 유저의 경우, 회원가입이 실패하고 실패 메세지를 표시해야 한다.', () => {
    wrapper.vm.form.emailAddress = '...'
    expect(wrapper.find('.failed').isVisible()).toBe(false)
    wrapper.vm.submitForm()
    wrapper.vm.$nextTick(null, () => {
    	expect(wrapper.find('.failed').isVisible()).toBe(true)
    })
})

가장 애를 먹었던 테스트로 $nextTick() 을 통해 렌더링이 완료된 후 테스트를 하더라도 계속 테스트가 실패했다. 렌더링을 트리거하는 비동기 함수의 실행이 $nextTick()을 통해 보장되지 않기 때문이었다. 이를 해결하기 위해서는 VTU 공식 문서에서 몇가지 해결책을 제시한다.

  • trigger() 함수 비동기 실행
  • plushPromises() 를 통한 태스크 큐 비우기
  • done() 함수의 사용

자세한 내용은 공식 문서를 참조

테스트 : registrationService

책에서는 moxios라는 외부 라이브러리를 사용해서 axios 모듈을 테스트했는데 나는 jest.mock('axios')를 통해 axios 모듈을 모킹하고 mockImplementation( ) 으로 axios.post의 가짜 함수를 구현해 테스트하였다.

import axios from 'axios';
import registrationService from '@/services/registration';

jest.mock('axios');

describe('services/registration', () => {
  beforeEach(() => {});

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('요청이 성공하면, 성공 응답이 호출자에게 전달되어야 한다.', async () => {
    axios.post.mockImplementation(() =>
      Promise.resolve({
        status: 200,
        data: {
          result: 'success',
        },
      })
    );

    const response = await registrationService.register();
    expect(response.data.result).toEqual('success');
  });

  it('요청이 실패하면, 실패 응답이 호출자에게 전달되어야 한다.', async () => {
    axios.post.mockImplementation(() =>
      Promise.reject({
        status: 400,
        data: {
          message: 'Bad request',
        },
      })
    );
    try{
      await registrationService.register();
    }catch(e){
      console.log(e)
      expect(e.data.message).toEqual('Bad request');
    }
  });
});

0개의 댓글

관련 채용 정보