개발노트

nuxt 프로젝트에 테스트코드프레임워크JEST 적용하기 본문

Vue/nuxt

nuxt 프로젝트에 테스트코드프레임워크JEST 적용하기

aloha2jh 2021. 8. 4. 14:40

1. 테스트코드 작성 목적과 의의

vue로 구현한 어플리케이션은 웹 브라우저에서 동작하며 UI가 있고 DOM을 조작한다. 

단위테스트 역시 DOM상태변경 및 확인과정이 한데 섞인 코드가 될수밖에 없다.

예를들면 어떤 컴포넌트가 전역 DOM에 마운팅 상태에서 렌더링 됬다면,

이 DOM을 초기화 하기 위해 DOM 조작을 통해 전처리 및 후처리가 필요하다.

또 마운트 된 컴포넌트에 대해서도 DOM을 조작해서 DOM상태를 확인하는 코드를 작성해야 한다.

(ex 로그인 폼 컴포넌트가 처음 마운팅될때 userId, password와 같은 상태값이 잘 초기화가 되어있는지 확인 등)

로그인 화면이아닌 테스트코드작성해서 유효성 검증 및 기능 검증을 예제코드로 진행할 예정이다.


1.Jest nuxt에 사용하기

1-1.의존성 설치

npm i -D babel-jest jest jest-vue-preprocessor @vue/test-utils

jest로 테스트할때 es6이상 문법 사용하기 위해 babel이 필요.

 

1-2.package.json 설정

"jest": {
    "verbose": true,
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      ".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
    },
    "moduleNameMapper": {
      "^vue$": "vue/dist/vue.common.js",
      "^~/(.*)$": "<rootDir>/$1"
    },
    "collectCoverage": true,
    "collectCoverageFrom": [
      "**/components/**/*.{js, vue}",
      "**/server/utils/*.{js}",
      "!**/node_modules/**"
    ],
    "coverageReporters": [
      "text-summary"
    ]
  }

 

moduleFileExtensions : 테스트 적용할(jest가 검색할) 파일 확장자 설정

moduleNameMapper : <rootDir>토큰을 사용해 루트 경로를 참조가능. 프로젝트에 맞는 경로로 지정

 

collectCoverage (modulePathIgnorePatterns) : 일치하는 경로의 모듈은 가져오지 않음

 

 

 

 

 

 

1-3.npm 명령어

"test": "jest --watchAll"

test명령어 실행시 자동으로 테스트파일 감시하도록 설정

watchAll - 테스트파일이 변화될때마다 다시 자동으로 테스트 실행해주는 명령어

 

 


 

2. jest 테스트 코드 실행
잘 설치되었는지 간단한 속성 describe, expect 테스트

자바스크립트 테스트 파일과 자바스크립트 파일의 차이는 중간에 .spec , .test의 여부

//<my-project>/tests/sum.test.js

describe('sum 메소드 테스트', () => {
  test('1 + 1 === 2 (Jest 테스트를 위함)', () => {
    expect(1+1).toBe(2)
  })
})

 

 

 


2-1. 모듈 실행되는지 테스트

 

// <my-project>/utils/sum.js
const sum = (a, b) => {
  return a + b
}
module.exports = sum


// <my-project>/tests/sum.tests.js
const sum = require('../utils/sum')
describe('sum 메소드 테스트', () => {
  test('1 + 1 === 2 (Jest 테스트를 위함)', () => {
    expect(sum(1,2)).toBe(3)
  })
})

 

 

 

// <my-project>/utils/sum.js
export sum = (a, b) => {
  return a + b
}


// <my-project>/tests/sum.tests.js
import sum from '../utils/sum';

sum(10,20);

describe('sum 메소드 테스트', () => {
  test('1 + 1 === 2 (Jest 테스트를 위함)', () => {
  	const result = sum(10, 20);
    result === 30
    expect(result).toBe(30)
  })
})

describe 라고 하는 jest에서 제공하는 api를 사용한다.

describe() - 연관된 테스트 케이스를 그룹화하는 API

특정기능을 점검하는게 testcase, testcase를 묶음하는게 describe

test() - 하나의 테스트케이스를 검증하는 API (어떤기능을 테스트할지 이름을 정의, 테스트코드 작성)

expect 기대되는값에 toBe 예상하는값

 

예외케이스

이렇게 입력시 false가 아니여야한다 이런식으로 사용할때

expect(result).not.toBe(false) 이런식으로 예외케이스에 사용하면된다.


 

2-3. Vue 컴포넌트 테스트

import export 키워드 사용하는 vue 컴포넌트 테스트 위해서는

.babelrc 파일을 루트에 추가

{ "presets": ["@babel/preset-env"] }

(바벨 프리셋 설정)

 

 


2-3 Vue 컴포넌트 테스트
(1) Header 컴포넌트가 vue 컴포넌트인지 테스트

싱글파일 컴포넌트에 반드시 <script></script>태그가 있어야한다 스크랩트 태그 없을경우 jest파일 테스트 불가능.

 

import { mount } from "@vue/test-utils";
import LoginLayer from "~/components/common/layer/LoginLayer.vue";
describe("로그인 컴포넌트", () => {
  let wrapper;
  beforeEach(() => {
    wrapper = mount(LoginLayer);
  });
  test("LoginLayer 컴포넌트는 Vue 인스턴스이다.", () => {
    expect(wrapper.isVueInstance()).toBeTruthy();
  });
});

jest 27 버전에서 해당 이슈 확인 하였고 (기본 테스트환경 jsdom에서 node로 바뀐후 적용이 안되는 문제)

jsdom을 추가하니 해결되었음( 그치만 deprecated 나오므로 추후 수정 필요)

"testEnvironment": "jsdom"

 

2-3 Vue 컴포넌트 테스트

(2) 2개의 oo클래스를 가지는지 테스트

  test("2개의 input_box 클래스를 가진다.", () => {
    expect(wrapper.findAll(".input_box").length).toBe(2);
  });

2-3 Vue 컴포넌트 테스트
(3) 마운팅시 화면에 그려지는지 테스트

 

import Vue from "vue";
import LoginLayer from "~/components/common/layer/LoginLayer.vue";
describe("로그인 컴포넌트", () => {
  test("컴포넌트 마운트시 화면에 그려져야 한다", () => {
    const instance = new Vue(LoginLayer).$mount(); //1
    console.log(instance);
    expect(instance).toBeInstanceOf(Vue); //2
  });
});

1. 만약 마운트 됬으면 인스턴스가 생성 됬을테고,

2. toBeInstanceOf(클래스)라는 속성으로 인스턴스가 생성된걸 체크한다.

jest 에서는 instance 결과값 콘솔로확인가능 

 

HeaderTool로 적용하면 mappedState vuex 관련 typeError 발생.  -> 아직해결못함.

 

+응용 

컴포넌트가 마운팅 되면 username이 존재하고 초기값으로 설정되어 있어야 한다

import Vue from "vue";
import LoginLayer from "~/components/common/layer/LoginLayer.vue";
describe("로그인 컴포넌트", () => {
  test("초기값 설정여부", () => {
    const instance = new Vue(LoginLayer).$mount(); //1
    console.log(instance);
    expect(instance.username).toBe(''); //2
  });
});

로그인컴포넌트 안에 data가 있고 username:''이 초기값이 설정되어 있을때.

 


3. 컴포넌트 테스트를 위한 뷰 공식 라이브러리

Vue Test Utils - vuejs를 위한 unit testing 공식 라이브러리

https://vue-test-utils.vuejs.org/guides/#getting-started

 

 

3-1 vue unit test 사용방법

  • 뷰인스턴스를 생성할 필요 없이 shallowMount 를 가져와서 바로 사용해서 사용하면 된다 
import { shallowMount } from "@vue/test-utils";
     // const instance = new Vue(HeaderTool).$mount();
    shallowMount(HeaderTool);  /////

 

  • vue test utils는 wrapper 기반 API  (대표적으로 vm vmodel, 
  • 마운트를 하게되면 wrapper가 생성되고 wrapper.vm이라고 하는게 instance

import LoginLayer from "~/components/common/layer/LoginLayer.vue";
import { shallowMount } from "@vue/test-utils";
describe("로그인 컴포넌트", () => {
  test("컴포넌트 마운트시 화면에 그려져야 한다", () => {
    const wrapper = shallowMount(LoginLayer);
    expect(wrapper.vm).toBeTruthy();
  });
});

3-2. find()를 이용한 컴포넌트 HTML요소 검색

컴포넌트가 화면에 부착되었을때 template안의 특정 html요소를 찾는다

 

import LoginLayer from "~/components/common/layer/LoginLayer.vue";
import { shallowMount } from "@vue/test-utils";
describe("로그인 컴포넌트", () => {
  test("id는 이메일형식이어야 한다", () => {
    const wrapper = shallowMount(LoginLayer,{
      data(){
        return{
          email:'test@etoos.com',
          password:'123'
        }
      }
    });
    
    //1
    const userId = wrapper.find('#mem_id').html();
    console.log(userId)
    
	//2
    const userId = wrapper.find('#mem_id');
    console.log(userId.element.value) //이렇게 value 가져오는것도 가능

  });
});

 


 

3-3. 로그인 유효성 검사

(1) 기능 구현

아이디랑 비밀번호 값이 없는지 검사

아이디가 이메일형식이아닐경우 (정규표현식)

function validateEmail(email) {
  const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test(String(email).toLowerCase());
}

export { validateEmail }

로그인시 그리고 회원가입시에도 검사를 해주어야 하기때문에, assets/mixins/validation.js 에 저장 하였음

 

 

 

 

컴포넌트의 computed에 isUserEmail 값이 입력값에 따라 true, false 바뀌는걸 확인할 수 있다.

 

1) disable 

<a href="javascript:;" :disable="isUserEmail" @click="onSubmitForm()" class="login_btn mt_20">로그인</a>


2) onSubmitForm 에서 

	onSubmitForm(){  
			if( this.isUserEmail && this.password ){ 
			 //값제대로입력했을때
			}else{
				return false;
			}
		}

 

 

3-3. 로그인 유효성 검사

(2) 로그인 유효성 검사 (테스트코드 작성)

import LoginLayer from "~/components/common/layer/LoginLayer.vue";
import { shallowMount } from "@vue/test-utils";
describe("로그인 컴포넌트", () => {
  test("id는 이메일형식이어야 한다", () => {
    const wrapper = shallowMount(LoginLayer,{
      data(){
        return{
          email:'tes ',
          password:'123'
        }
      }
    });
    const userId = wrapper.find('#mem_id');
    console.log('inputbox값', userId.element.value) 
    const result =  wrapper.vm.isUserEmail;
    expect(result).toBeTruthy();
  });
});

1. 테스트케이스에 입력하고자 하는 데이터 설정

2. wrapper로 찾은 mem_id라는 userEmail형식받는 inputBox에 value값을 가져올때

3. 위에 작성한 user email 유효성 검사 computed 값을 통해 가져올수 있다.

 

이메일형식에 맞지않기때문에 false

 

 

3-3. 로그인 유효성 검사
(3) 사용자 관점의 로그인 테스트 코드 작성

 

<p class="msg msg-vali">
  <span class="vali-txt vali-email" v-if="!isUserEmail ">올바른 이메일 형식을 입력해 주세요</span> 
  <span class="vali-txt vali-not-empty" v-if="!email || !password">id와 password 입력해 주세요</span>
</p>

사용자에게 안내문구가 나오는지 테스트케이스를 작성해서 검증해보기 위해서

기존 UI에 두가지 안내문구 작성 

 

3-3. 로그인 유효성 검사
(3) 이메일 형식이 아닐때 해당 안내문구(.vali-email)가 나오는지 테스트

let wrapperFalsy = shallowMount(LoginLayer,{
    data(){
      return{
        email:'tes',
        password:'123'
      }
    }
  });

 test("이메일 형식이 아닐때 안내 메세지를 보여준다",()=>{
    const warningTxt = wrapperFalsy.find('.vali-email');
    expect(warningTxt.exists()).toBeTruthy();
  })
  
  //마찬가지로 비밀번호 입력안했을때 안내메세지 확인
  test("비밀번호 값을 입력안했을때 안내메세지를 보여준다",()=>{ 
    const warningTxt = wrapperFalsy.find('.vali-not-empty');
    expect(warningTxt.exists()).toBeTruthy();
  })

 

 

 

 

4. 비동기 데이터 호출 테스트

import { fetchHeader, headerAside, headerSearch } from "~/assets/api/layout";

describe("fetch data ", () => {
  test("fetchHeader api 호출시 response 성공해서 데이터를 받는다 ", (done) => {
    fetchHeader().then((res) => {
      expect(res).toBeTruthy();
      done();
    });
  });
  test("headerAside api 호출시 response 성공해서 데이터를 받는다 ", (done) => {
    headerAside().then((res) => {
      expect(res).toBeTruthy();
      done();
    });
  });

  test("headerSearch api 호출시 response 성공해서 데이터를 받는다 ", (done) => {
    headerSearch().then((res) => {
      expect(res).toBeTruthy();
      done();
    });
  });
});

 

5. Jest Hooks

Jest에서 beforeAll, afterAll, beforeEach, afterEach 4가지 전역 함수 제공

 

선언된 describe 시작할때 beforeAll, 선언된 describe 끝나기전 afterAll

각테스트마다 테스트 시작전 beforeEach, 테스트 끝난후 afterEach 가 실행된다

 

describe("Jest Hooks", () => {
  beforeAll(() => {
    console.log("beforeAll");
  });
  afterAll(() => {
    console.log("afterAll");
  });
  beforeEach(() => {
    console.log("beforeEach");
  });
  afterEach(() => {
    console.log("afterEach");
  });

  test("1", () => {
    expect(1 + 1).toBe(2);
    console.log("test 1 is done!");
  });
  test("2", () => {
    expect(2 + 1).toBe(3);
    console.log("test 2 is done!");
  });
});

 

6.  Jest syntax

 

(1) toBe

number, string 은 상관없지만 객체 배열의 경우 실패 toEqual toStrictEqual 같은 깊은(deep)동등비교 사용해야한다

 

(2) toHaveProperty

객체에서 찾고자 하는 속성의 존재여부, 해당 값 확인

 

(3) jest.fn() 

jest.fn()은 모의 함수를 반환

모의(Mock) 실제로 동작하지만 준비된 값만 반환

이게그러면 버튼을클릭한다거나 했을때 action이 발생되는지 테스트할때

액션을 실제로 실행시키는게 아니라 액션에 jest.fn을 선언해두고 (실제 함수를 모의로 덮어쓰고서)

action을 실행시키는지 - toHaveBeenCalled() 로 검사

    expect(actions.testAction ).toHaveBeenCalled()

 

 

7. vue test util (VTU)

컴포넌트 렌더링(mountings) 함수

 

함수 반환 특징
mount() Wrapper 마운트(하위 컴포넌트 렌더링)
shallowMonut() Wrapper 얕은 마운트(하위 컴포넌트 Stub-스텁* )
render() Promise 문자열로 렌더링하고 Cheerio 객체(promise) 반환
renderToString() Promise THML로 렌더링

render, renderToString 사용시 @vue/server-test-utils 설치필요

 

 

*스텁: 실제로 동작하는 것처럼 보이는 객체를 의미

 

 

 


https://vue-test-utils.vuejs.org/guides/using-with-vuex.html

두 값다 null일때,

한값만 null 일때,

이메일 형식이 맞지 않을때,

login action이 dispatch 되지 않는것인지 테스트

 

두값다 맞게 입력했을때,

login action이 dispatch 되는지 테스트

 

 


jest.config.js 설정

jest.config.js 파일에 대상을 지정할수 있음

testMatch 라는 속성이 없으면test폴더 밑에다가만 javascript test파일을 작성해야함

 


참고 사이트

 

https://changjoopark.medium.com/nuxt-js%EC%97%90-jest-storybook-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-7f819ef6d4f8

 

 

 

 

https://heropy.blog/2020/05/20/vue-test-with-jest/

'Vue > nuxt' 카테고리의 다른 글

nuxt build  (0) 2021.09.01
vue transition  (0) 2021.08.03
infinity scrolling  (0) 2021.07.23
vuex helper 사용하기  (0) 2021.07.23
vue-awesome-swiper  (0) 2021.07.23