개요
app을 빌드하고 실행하면, vue 인스턴스에 라이브러리들이 import되어서 빌드되면서 라이브러리의 필요한 기능을 사용할 수 있었습니다.
jest에서는 컴포넌트를 mount해서 실행합니다. jest가 컴포넌트를 실행하다 보니, 컴포넌트가 의존하는 라이브러리의 경로를 못 찾거나 라이브러리의 기능을 온전히 실행하지 못하는 경우가 많습니다. 그래서 test코드나 jest 설정으로 의존하는 라이브러리를 설정하거나 테스트하는 컴포넌트에 주입해줘야 jest로 제대로 기능을 테스트할 수 있습니다.
팀에서는 제가 최초로 웹 테스트 방법을 개척하고 있기 때문에 기술문서를 많이 찾아보면서 문제를 해결해 나갔습니다.
jest에 컴포넌트에서 사용하는 라이브러리에 맞춤 설정하기
⚡️ Vuetify를 사용할 경우 설정
jest 실행 환경 전에 vuetify를 vue 인스턴스에 추가하는 js파일 jest 설정에 추가
1. package.json에 jest 설정에 다음 옵션 추가
// 📄package.json
{
"name": "프로젝트 명",
"jest": { // 👈 jest 설정
// ...
"setupFilesAfterEnv": [
"<rootDir>/jest-setup.js" // 👈vuetify를 Vue 인스턴스에 넣는 js 파일
]
// ...
}
},
2. jest-setup.js 파일 작성
package.json과 같은 경로에 위치시켰습니다.
// 📄 jest-setup.js
import Vue from 'vue';
import Vuetify from 'vuetify';
Vue.use(Vuetify);
3. login.test.js 테스트 코드 작성
jest 가이드 문서에서는 디렉토리는 보통 테스트할 컴포넌트와 동일한 경로에 두는 것을 추천함.
컴포넌트 mount, shallowMount할 때, vuetify 생성자 추가하기(참고: vuetify Unit Testing)
// 📄src/Modules/Login/login.test.js
//..
import Vuetify from 'vuetify'; //👈
describe('로그인 테스트', () => {
let vuetify; //👈
beforeEach(() => {
vuetify = new Vuetify(); //👈
});
it('로그인 요청 성공 테스트', async () => {
const shallowWrapper = shallowMount(BaseHeader, { router, vuetify }); //👈
}
}
⚡ 테스트 코드에서 Vue Router 사용해서 테스트하는 방법
참고: Vue 테스팅 핸드북
주의할 점
- 전역으로 Vue router를 설치하지 않는다. createLocalVue() 사용해서 vue-router를 주입합니다.
⚡ vuex의 state, actions, mutations, getters 등을 사용하는 컴포넌트 테스트 방법
참고 : Vue Test Utils-Using with Vuex
import Vuex from 'vuex'; // 👈
describe('로그인 테스트', () => {
const localVue = createLocalVue(); // 👈 해당 테스트에만 vuex를 사용하기 위해서 임시vue 인스턴스에 vuex 의존성을 주입
localVue.use(Vuex);
const store = new Vuex.Store({ // 👈 store 객체 생성
state: {
user: {
gUserInfo: {}
}
},
actions: {
fetchUserInfo: jest.fn() // 👈 jest mock function
}
});
const shallowWrapper = shallowMount(BaseHeader, { localVue, store, router, vuetify }); // 👈 임시 vue 인스턴스와 store 주입한 wrapper 객체 생성
}
어려웠던 점과 해결 방법 😩😀
1. 😩 Node 환경에서 import 문법 지원이 안 되는 문제
에러 메시지
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { mount, createLocalVue } from '@vue/test-utils';
SyntaxError: Cannot use import statement outside a module
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1479:14)
원인
- test 코드는 javascript가 Node.js 플랫폼에서 실행됩니다. commonjs 모듈을 사용하는 node.js가 import를 인식하지 못합니다.
해결방법
1. npm i -D babel-jest vue-jest
2. babel.config.js 설정
module.exports = {
// ...
env: {
test: {
plugins: ['transform-es2015-modules-commonjs']
}
}
};
3. package.json에 jest 설정
js와 vue 확장자 파일을 jest 테스트 가능한 코드르 변환 설정
// package.json
{
"name": "rms-fe",
// ...
"jest": { //👈 jest 설정
"moduleFileExtensions": [
"js",
"vue"
],
"transform": { //👈js와 vue 파일을 jest 테스트 가능한 코드로 변환 설정
"^.+\\.js$": "babel-jest",
".*\\.(vue)$": "vue-jest"
}
},
// ...
}
이 설정을 해서, Jest는 vue 파일을 jest가 이해할 수 있도록 transform 할 수 있고, import 문법을 사용할 수 있게 됩니다.
이 설정을 통해서 test.js 파일 안에서 import 문법을 사용해서 module을 가져올 수 있게 되었습니다.
참고 : https://ui.toast.com/weekly-pick/ko_20180822
2. 😩 jest가 '@' 경로 alias를 인식하지 못하는 문제 발생
에러 메시지
Cannot find module '@/Modules/LineChart' from 'src/Modules/AdminSetting/Chart/components/chart.vue'
원인
@ 경로 alias를 jest가 인식하지 못해서 발생함.
해결 방법
package.json에 jest 설정
- webpack 설정으로 alias를 설정한 것처럼, jest도 alias설정을 해줌
"jest": {
// ...
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1" //👈 webpack 설정으로 alias를 설정한 것처럼, jest도 alias설정을 해줌
},
// ...
}
3. 😩 web app의 화면 요소들을 테스트
jest의 기본 테스트 환경은 Node.js입니다. 브라우저 환경처럼 렌더링을 거쳐서 화면 요소를 DOM 처럼 가지고 있지 않습니다.
에러 메시지를 보면, window 객체부터 못 찾아서 에러가 발생한 것을 알 수 있습니다.
에러 메시지
[vue-test-utils]: window is undefined, vue-test-utils needs to be run in a browser environment. You can run the tests in node using jsdom See https://vue-test-utils.vuejs.org/guides/#browser-environment for more details.
원인
jest의 테스트 코드 파일이 node 환경으로 실행되기 때문에 화면 요소 객체를 만들지 못 함.
해결 방법
참고 : https://jestjs.io/docs/configuration#testenvironment-string
브라우저의 실행 환경과 같이 만들어 줘야 합니다.
Jest 27버전 문서를 확인해 보니, 주석으로 test.js파일에 브라우저와 같은 환경을 설정할 수 있다고 나와있었습니다.
파일 가장 상단에 아래 주석을 입력해주면 됩니다.
설정 파일 : 컴포넌트를 마운트해서 사용할 test.js 파일 상단에 아래 주석을 작성.
/**
* @jest-environment jsdom
*/
4. 😩 jest에서 vuetify 태그<v-container> | <v-btn> ...을 인식할 수 있게 하기
에러메시지
원인
테스트 코드에서 mount한 컴포넌트에 vuetify의 테그가 있으면, jest는 이 태그들을 인식할 수 없습니다.
해결 방법
1. package.json 경로에 jest-setup.js파일 만들기
프로젝트 홈 경로/jest-setup.js
import Vue from 'vue';
import Vuetify from 'vuetify';
Vue.use(Vuetify);
2. package.json에 jest 'setupFilesAfterEnv' 설정 추가하기
프로젝트 홈 경로/package.json
{
"name": "rms-fe",
"jest": {
"setupFilesAfterEnv": [ // 👈 이 부분 추가
"<rootDir>/jest-setup.js" //👈 <rootDir> 그대로 사용하기 <rootDir>은 프로젝트 홈 경로를 지칭함.
]
},
// ...
}
Vuetify의 v-container 태그를 사용하는 login 페이지 테스트 성공
5. 😩 jest가 Vuelidate 라이브러리를 인식 못 함.
Vuelidate를 통해서 required 검증을 하는 input을 테스트 하는데, 컴포넌트에서 vuelidate의 객체인 $v를 인식하지 못했음.
원인
jest는 vuelidate의 $v객체를 모름.
해결 방법
test.js 테스트 파일에 Vue 인스턴스에 vuelidate라이브러리를 주입해주는 방식으로 해결함.
6. 😩 프로젝트에서 사용하는 router를 test 코드에 가져다 사용하기
TypeError: routes.forEach is not a function
원인
테스트 코드에 import한 @/router/index.js가 export default로 router 객체를 VueRouter 생성자의 매개변수로 넣는 것이 문제였음.
new VueRouter()의 매개변수는 Iterable 타입이어야 함.(=배열)
해결 방법
객체를 반환하는 파일을 import 했음. router/index.js에서 router 정보를 가지는 routes 배열을 넣어줘야 함.
@/router/index.js
const routes = [
// ...
{
path: '/group',
name: 'GROUP',
component: () => import('@/pages/Group.vue')
},
{
path: '/usersetting',
name: 'user-setting',
component: () => import('@/pages/UserSetting.vue')
}
];
const router = new VueRouter({
mode: 'history',
scrollBehavior() {
return { x: 0, y: 0 };
},
routes
});
export default router;
export { routes }; // 👈 배열형의 routes 정보 배열을 export함.
login.test.js
/**
* @jest-environment jsdom
*/
import Vue from 'vue';
// ...
import { routes } from '@/router/index.js'; //👈 배열 형태를 import 함.
const localVue = createLocalVue();
localVue.use(VueRouter);
describe('로그인 테스트', () => {
//...
it('로그인 요청 성공 테스트', async () => {
const router = new VueRouter({ routes });
const wrapper = mount(Login, { localVue, router });
const loginBtn = wrapper.find('[element-test="login-btn"]');
const id = wrapper.find('[element-test="id-input"]');
const password = wrapper.find('[element-test="password-input"]');
id.setValue('admin');
password.setValue('admin');
// login 컴포넌트의 data property에 값이 들어가는 지 확인
await loginBtn.trigger('click');
// 로그인 api 응답 success 확인
//라우터가 intro로 가는 지 확인
expect(wrapper.vm.$route.name).toBe('FUEL CELL');
});
});
7. 😩 jest가 export를 인식 못 함.
에러메시지
SyntaxError: Unexpected token 'export'
원인
node_modules의 코드까지 jest가 테스트하려 함.
해결 방법
package.json에 jest 설정 추가
{
"jest": {
//...
"transformIgnorePatterns": [ "<roodDir>/node_modules/(?!@swish)" ]
// ...
}
}
실무 로그인 테스트 코드
프로젝트 홈 경로/src/Modules/Login/index.vue
<template>
<v-container fluid class="login d-flex justify-center align-center">
<v-card width="320" height="500" class="login_box d-flex flex-column justify-end align-center px-10 pt-15">
<img src="" alt="" />
<p class="text-h4 font-weight-bold pt-10 mb-16">Login</p>
<v-text-field v-model="sId" :error-messages="sIdError" placeholder="ID" dense outlined element-test="id-input"></v-text-field>
<v-text-field
v-model="sPassword"
type="password"
class="mx-0"
:error-messages="sPasswordError"
dense
outlined
placeholder="PASSWORD"
element-test="password-input"
></v-text-field>
<v-container class="mb-9 pa-0">
<v-btn block rounded color="#741eec" class="" @click="loginSubmit()" element-test="login-btn">Login</v-btn>
<p class="text-center red--text" v-if="sSubmitStatus === ''"><br /></p>
<p class="text-center red--text" v-if="sSubmitStatus === 'OK'">Thanks for your submission!</p>
<p element-test="submit-status-message" class="text-center red--text" v-if="sSubmitStatus === 'ERROR'">Please fill the form correctly.</p>
<p class="text-center red--text" v-if="sSubmitStatus === 'PENDING'">Sending...</p>
</v-container>
<p class="text-caption grey--text">Copyright ⓒ2021 MACHBASE</p>
</v-card>
</v-container>
</template>
<script>
import { required } from 'vuelidate/lib/validators';
export default {
name: 'login',
validations: {
sId: { required },
sPassword: { required }
},
computed: {
sIdError() {
let errors = [];
if (!this.$v.sId.$dirty) return errors;
if (!this.$v.sId.required) {
errors.push('input id.');
}
return errors;
},
sPasswordError() {
let errors = [];
if (!this.$v.sPassword.$dirty) return errors;
if (!this.$v.sPassword.required) {
errors.push('input password.');
}
return errors;
}
},
methods: {
loginSubmit() {
this.$v.$touch();
if (this.$v.$dirty === true) {
this.sSubmitStatus = 'ERROR';
return;
} else {
this.sSubmitStatus = 'PENDING';
setTimeout(() => {
this.sSubmitStatus = 'OK';
this.$router.push('/intro');
}, 500);
}
}
},
data() {
return {
sId: '',
sPassword: '',
sSubmitStatus: ''
};
}
};
</script>
<style lang="scss" scoped>
.login {
background-color: $BACKGROUND_GRAY;
height: 100vh;
}
.v-input {
width: 100%;
}
</style>
프로젝트 홈 경로/src/Modules/Login/index.test.js
/**
* @jest-environment jsdom
*/
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import Login from './index.vue';
import Vuelidate from 'vuelidate'; // 👈 vuelidate를 사용할 경우 추가
Vue.use(Vuelidate); // 👈 vuelidate를 사용할 경우 추가
describe('로그인 테스트', () => {
it('element 존재 테스트', () => {
const wrapper = mount(Login);
//아이디와 패스워드 입력창 존재
const idInput = wrapper.find('[element-test="id-input"]');
const pwInput = wrapper.find('[element-test="password-input"]');
const loginBtn = wrapper.find('[element-test="login-btn"]');
expect(idInput.exists()).toBe(true);
expect(pwInput.exists()).toBe(true);
expect(loginBtn.exists()).toBe(true);
});
it('로그인 id와 pw null submit 요청했을 때, submit 안 되는 지 테스트', async () => {
const wrapper = mount(Login);
const loginBtn = wrapper.find('[element-test="login-btn"]');
const id = wrapper.find('[element-test="id-input"]');
const password = wrapper.find('[element-test="password-input"]');
// id.setValue('123'); // 👈 값 대입
expect(id.element.value).toBe('');
expect(password.element.value).toBe('');
await loginBtn.trigger('click');
const submitMessage = wrapper.find('[element-test="submit-status-message"]');
expect(submitMessage.exists()).toBe(true);
expect(submitMessage.text()).toContain('Please fill the form correctly.');
});
});
'Vue.js' 카테고리의 다른 글
[vue.js]vue-router 튜토리얼-2 (0) | 2021.09.07 |
---|---|
vue.js에서 SASS 사용하기 (0) | 2021.09.04 |
[Vue Test Utils] Vue 테스트 코드 작성 방법1️⃣- 테스트 환경 설정 및 간단한 테스트 코드 작성 방법 (0) | 2021.08.05 |
[Chart.js] Vue에서 사용하기. (7) | 2021.07.26 |
[Vue] Vuetify 템플릿 적용 개발 팁 (0) | 2021.07.18 |
댓글