๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Vue

๐ŸŽฒ vue + jest๋ฅผ ์ด์šฉํ•œ login ๊ตฌํ˜„

by frontChoi 2023. 4. 11.
๋ฐ˜์‘ํ˜•

๐Ÿคน๐Ÿป‍โ™‚๏ธ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ

๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ์„ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•˜๋ฉฐ, ์ž…๋ ฅ๊ฐ’์€ id/pw๋งŒ ์กด์žฌํ•œ๋‹ค.

 

 

๐ŸšŽ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค

  • id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.
  • id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™
  • ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

 

๐Ÿงฉ ์‚ฌ์ „์ž‘์—…

* LoginPage.vue๋ฅผ ๋งŒ๋“ ๋‹ค.
* ํ…Œ์ŠคํŠธ ํ•  loginpage.test.js๋ฅผ ๋งŒ๋“ ๋‹ค

<!-- LoginPage.vue -->
<template>
  <div>
  </div>
</template>
<script>
export default {
  setup() {
    //
  },
};
</script>
<style></style>

๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„์–ด์žˆ๋Š” vue ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

//loginpage.test.js
import LoginPage from '../LoginPage.vue';
import { shallowMount } from '@vue/test-utils';
describe('LoginPage.vue ๋‹จ์œ„ํ…Œ์ŠคํŠธ', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(LoginPage);
  });
  test('id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.', () => {});
});

shallowMount๋ฅผ ํ†ตํ•ด์„œ vueํŒŒ์ผ์„ ๊ฐ€์ ธ์˜จ๋‹ค. shallMount๋Š” jest์—์„œ ๋ Œ๋”๋ง๋œ vue์ปดํผ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

๐ŸŽฎ  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 1 :  id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.

 

<!-- LoginPage.vue -->
<template>
  <div>
    <form>
      <label>Id :</label>
      <input type="text" id="id" v-model="form.id" /> <br /><br />

      <label>Pw :</label>
      <input type="password" id="password" v-model="form.password" /> <br /><br />
    </form>
  </div>
</template>
<script>
import { reactive } from 'vue';

export default {
  setup() {
    const form = reactive({
      id: '',
      password: '',
    });

    return {
      form,
    };
  },
};
</script>
<style></style>

๊ฐ„๋‹จํ•œ id,pw๋ฅผ ์ž…๋ ฅ์„ ์œ„ํ•ด form์„ ๋งŒ๋“ ๋‹ค.

  • reactive๋ฅผ ์ด์šฉํ•˜์—ฌ id,password๋ฅผ ๋งŒ๋“ ๋‹ค.(v-model์— ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด)
//loginpage.test.js
import LoginPage from '../LoginPage.vue';
import { shallowMount } from '@vue/test-utils';
describe('LoginPage.vue ๋‹จ์œ„ํ…Œ์ŠคํŠธ', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(LoginPage);
  });
  test('id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.', async () => {
    const id = await wrapper.find('#id');
    const pw = await wrapper.find('#password');
    const idValue = id.element.value;
    const pwValue = pw.element.value;
    
    await wrapper.find('form').trigger('submit.prevent');
    if (!idValue || !pwValue) {
      alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
    }
  });
});

 

๊ธฐ์กด loginpage.test.js ์—์„œ ์ถ”๊ฐ€ํ•œ ์‚ฌํ•ญ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค

 

  • findํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด id,pw๋ฅผ ์ฐพ๋Š”๋‹ค.
  • findํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด id,pw ์ฐพ์€๊ฒƒ์„ value๋ฅผ ํ†ตํ•ด idValue,pwValue์— ํ• ๋‹นํ•œ๋‹ค.
  • idValue ๋˜๋Š” pwValue์— ๋นˆ๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.') ๋ฅผ ๋„์šด๋‹ค.

 

ํ•˜์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค. ์•„๋ž˜ ์—๋Ÿฌ๋ฅผ ๋ณด๋ฉด Not implemented์ด๋‹ค. ๋Œ€์ถฉ window์— alert๊ฐ€ ์—†๋‹ค๋Š” ๋œป์œผ๋กœ ์ถ”์ •๋œ๋‹ค.

 

 

//loginpage.test.js
import LoginPage from '../LoginPage.vue';
import { shallowMount } from '@vue/test-utils';
/* ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ๋ถ€๋ถ„! alert์„ mockImplementation ํ•ด์ค˜์•ผ ํ•œ๋‹ค. */
jest.spyOn(window, 'alert').mockImplementation(() => {});
describe('LoginPage.vue ๋‹จ์œ„ํ…Œ์ŠคํŠธ', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(LoginPage);
  });
  test('id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.', async () => {
    const id = await wrapper.find('#id');
    const pw = await wrapper.find('#password');
    const idValue = id.element.value;
    const pwValue = pw.element.value;
    if (!idValue || !pwValue) {
      alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
    }
  });
});

success

์ถ”๊ฐ€ ํ•œ ํ›„์—๋Š” ์„ฑ๊ณต์ ์œผ๋กœ Pass ํ•œ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

 

<!-- LoginPage.vue -->
<template>
  <div>
    <form @submit.prevent="submit">
      <label>Id :</label>
      <input type="text" id="id" v-model="form.id" /> <br /><br />

      <label>Pw :</label>
      <input type="password" id="password" v-model="form.password" /> <br /><br />
    </form>
  </div>
</template>
<script>
import { reactive } from 'vue';

export default {
  setup() {
    const form = reactive({
      id: '',
      password: '',
    });
	
    //submit ํ•จ์ˆ˜ ์ถ”๊ฐ€!
    const submit = () => {
      if (!form.id || !form.password) {
        alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
        return false;
      }
    };

    return {
      form,
      submit,
    };
  },
};
</script>
<style></style>
//loginpage.test.js
import LoginPage from '../LoginPage.vue';
import { shallowMount } from '@vue/test-utils';
//alert ๊ฐ€์งœ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ
jest.spyOn(window, 'alert').mockImplementation(() => {});
describe('LoginPage.vue ๋‹จ์œ„ํ…Œ์ŠคํŠธ', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(LoginPage);
  });
  test('id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.', async () => {
    const id = await wrapper.find('#id');
    const pw = await wrapper.find('#password');
    const idValue = id.element.value;
    const pwValue = pw.element.value;

    await wrapper.find('form').trigger('submit.prevent');

    if (!idValue || !pwValue) {
      //alert์„ ํ˜ธ์ถœํ•˜๋ฉด์„œ alert์— ์–ด๋–ค ์ธ์ž๋ฅผ ๋„ฃ์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ ํ•œ๋‹ค.
      expect(alert).toBeCalledWith('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
    }
  });
});

์ฒ˜์Œ์—๋Š” test์ฝ”๋“œ์—์„œ alert์„ ํ˜ธ์ถœํ•˜์˜€์œผ๋‚˜, ์ž‘์„ฑํ•˜๋‹ค๋ณด๋‹ˆ ์›๋ณธํŒŒ์ผ์—์„œ alert์„ ํ˜ธ์ถœํ•˜๋Š”๊ฒŒ ๋งž๋Š”๊ฒƒ ๊ฐ™์•„, LoginPage.vue์— id,pw์— ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  loginpage.test.js์—์„œ๋Š” idValue,pwValue๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alertํ•จ์ˆ˜๊ฐ€ "id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”." ๋ฅผ ํ˜ธ์ถœํ•˜์˜€๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด toBeCalledWith๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

 

//loginpage.test.js
import LoginPage from '../LoginPage.vue';
import { shallowMount } from '@vue/test-utils';
//alert ๊ฐ€์งœ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ
jest.spyOn(window, 'alert').mockImplementation(() => {});
describe('LoginPage.vue ๋‹จ์œ„ํ…Œ์ŠคํŠธ', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(LoginPage);
  });
  test('id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.', async () => {
    await wrapper.find('form').trigger('submit.prevent');
	//๋น„์–ด์žˆ์„๊ฒฝ์šฐ LoginPage.vue์—์„œ alert๋ฅผ ํ˜ธ์ถœํ•  ๊ฒƒ์ด๊ณ , ์—ฌ๊ธฐ์—์„œ ํ˜ธ์ถœ์—ฌ๋ถ€๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณ€๊ฒฝํ•˜์˜€๋‹ค.
    expect(alert).toBeCalledWith('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
    // mock์€ ํ•˜๊ณ  ๋‚˜์„œ clearํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค
    alert.mockClear();
  });
});

if๋ฌธ์„ ์‚ญ์ œํ•˜์˜€๋‹ค. ์ด๋ฏธ submit ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฐ’์ด ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— loginpage.test.js์—์„œ ํ˜ธ์ถœ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๋„๋ก ๋ฐ”๊พธ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ mockClear๋ฅผ ํ†ตํ•ด ๋น„์›Œ์คฌ๋‹ค.

 

 

์ด๋กœ์„œ 4๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ค‘ ํ•œ๊ฐ€์ง€๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

  • id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.
  • id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™
  • ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

 

๐Ÿฅ…  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 2 :  id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™

๐ŸŽพ ์‚ฌ์ „์ž‘์—…

 

//loginpage.test.js
//์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€
import { login } from '@/api/login';
//api/login์„ ์ „์ฒด๋กœ mock ๋งŒ๋“ค๊ธฐ
jest.mock('@/api/login');

๋กœ๊ทธ์ธ api๋ฅผ import ํ•˜๊ณ  @/api/login ๋ถ€๋ถ„์„ mock ํ•ด์ค€๋‹ค.

 

<!-- LoginPage.vue -->
<template>
  <div>
    <form @submit.prevent="submit">
      <label>Id :</label>
      <input type="text" id="id" v-model="form.id" /> <br /><br />

      <label>Pw :</label>
      <input type="password" id="password" v-model="form.password" /> <br /><br />
    </form>
  </div>
</template>
<script>
import { reactive } from 'vue';
import { login } from '@/api/login';
export default {
  setup() {
    const form = reactive({
      id: '',
      password: '',
    });

    const submit = () => {
      if (!form.id || !form.password) {
        alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
        return false;
      }
      
      login({
        UserId: form.id,
        PassWord: form.password,
      });
    };

    return {
      form,
      submit,
    };
  },
};
</script>
<style></style>

> ์—ฌ๊ธฐ์„œ๋Š” api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค. submit์„ ํ˜ธ์ถœ ํ• ๋•Œ loginํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ž loginํ•จ์ˆ˜๋Š” axios๋ฅผ ํ†ตํ•ด์„œ ํšŒ์›์ธ์ฆํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

 

test('id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™', async () => {
    login.mockImplementation(() => {});
    await wrapper.find('#id').setValue('chleorjs37@gmail.com');
    await wrapper.find('#password').setValue('testpassword');

    await wrapper.find('form').trigger('submit.prevent');

    await expect(login).toHaveBeenCalled();
  });

>  login ๋ถ€๋ถ„์„ ์ฆ‰์‹œ ๊ตฌํ˜„ํ•œ๋‹ค(๋’ค์—์„œ Promiseํ˜•ํƒœ๋กœ ๋ฐ”๊ฟ€ ์˜ˆ์ •์ด๋‹ค.) ๊ทธ๋ฆฌ๊ณ  login ํ˜ธ์ถœ ํ™•์ธ์„ ์œ„ํ•ด toHaveBeenCalled()๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

 

ํ•˜์ง€๋งŒ loginํ•จ์ˆ˜๋Š” axiosํ˜ธ์ถœ ์ด๋ฏ€๋กœ return์„ Promise์œผ๋กœ ๋ฆฌํ„ดํ•œ๋‹ค. ๊ทธ๋ฆฌํ•˜์—ฌ login.mockImplementation(() => {}) ๋ถ€๋ถ„์„ Promiseํ˜•ํƒœ๋กœ return ํ•˜๊ฒŒ ๋งŒ๋“ค์ž.

 

test('id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™', async () => {
    //Promiseํ˜•ํƒœ๋กœ ๋ฆฌํ„ดํ•˜๋ฉด์„œ status๋Š” 201๋กœ ๋Œ๋ ค์ค€๋‹ค.
    login.mockImplementation(() => {
      return new Promise(resolve => {
        resolve({
          status: 201,
        });
      });
    });
    await wrapper.find('#id').setValue('chleorjs37@gmail.com');
    await wrapper.find('#password').setValue('testpassword');

    await wrapper.find('form').trigger('submit.prevent');

    await expect(login).toHaveBeenCalled();
    expect(alert).toBeCalledWith('๋กœ๊ทธ์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
  });

login๋ถ€๋ถ„์„ Promiseํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค. ์ƒํƒœ๋ฅผ 201๋กœ ๋Œ๋ ค์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์„ฑ๊ณต์ ์ผ ๊ฒฝ์šฐ alert์„ ํ˜ธ์ถœํ•˜์˜€๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด toBeCalledWith์œผ๋กœ ํ™•์ธํ•˜์˜€๋‹ค.

 

ํ•˜์ง€๋งŒ ์•„์ง router.push('/main') ์„ ํ˜ธ์ถœํ•˜์˜€๋Š”์ง€๋Š” ํ…Œ์ŠคํŠธ ํ•˜์ง€ ์•Š์•˜๋‹ค.

//loginpage.test.js
import { useRouter } from 'vue-router';//vue-router mock
jest.mock('vue-router', () => ({
  useRouter: jest.fn(() => ({
    push: () => {},
  })),
}));

์‹ค์ œ router๊ฐ€ ์•„๋‹Œ ๊ฐ€์งœ ํ˜ธ์ถœ์„ ํ•˜๊ธฐ์œ„ํ•ด vue-router๋ฅผ mock๋งŒ๋“ค์–ด์ฃผ๊ณ , ๋นˆ ํ•จ์ˆ˜ push๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

 

 

//loginpage.test.js
//push ํ˜ธ์ถœ ์—ฌ๋ถ€ ๊ฐ์‹œ๋ฅผ ์œ„ํ•ด ๊ฐ€์งœ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ
let push = null;
beforeEach(() => {
    push = jest.fn();
    useRouter.mockImplementation(() => ({
      push,
    }));
    wrapper = shallowMount(LoginPage);
});

useRouter์—์„œ ์ฆ‰์‹œํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ, push๋ฅผ ๊ฐ€์งœ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ ๋‹ค.

 

test('id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™', async () => {
    // ์•ž ์ฝ”๋“œ ์ƒ๋žต
	expect(push).toHaveBeenCalledWith('/main');
})

push๊ฐ€ '/main'์œผ๋กœ ํ˜ธ์ถœํ•˜์˜€๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๊ธฐ ์œ„ํ•ด toHaveBeenCalledWith๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

ํ•˜์ง€๋งŒ ์˜ค๋ฅ˜๊ฐ€ ๋œฌ๋‹ค. LoginPage ์—์„œ๋Š” ์‹ค์ œ๋กœ ํ˜ธ์ถœํ•œ์ ์ด ์—†๋‹ค๋Š”๊ฒƒ ๊ฐ™๋‹ค. "LoginPage.vue"์—์„œ router ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•˜์ž

 

 

//LoginPage.vue
const submit = () => {
      if (!form.id || !form.password) {
        alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
        return false;
      }

      login({
        UserId: form.id,
        PassWord: form.password,
      })
        .then(value => {
          const { status } = value;
          if (status === 201) {
            alert('๋กœ๊ทธ์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
            //router.push ํ˜ธ์ถœ
            router.push('/main');
          }
        })
        .catch(err => {});
    };

vueํŒŒ์ผ์—์„œ router.push('/main')์„ ํ˜ธ์ถœํ•˜์˜€๋”๋‹ˆ, PASS๋ฅผ ๋œจ๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ด๋กœ์„œ 4๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ค‘ ๋‘๊ฐ€์ง€๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

  • id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.
  • id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™
  • ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

๐Ÿœ  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 3 : ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

> ๋กœ๊ทธ์ธ์ด ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๋Š” http code๊ฐ€ 201๋ฉด์„œ, code:'NOT_VALID' ์ธ ๊ฒฝ์šฐ "๋กœ๊ทธ์ธ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋‹ค" ๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค.

 

test('๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ', () => {
    login.mockImplementation(() => {
      return new Promise(resolve => {
        resolve({
          status: 500,
          code: 'NOT_VALID',
        });
      });
    });
  });

์ผ๋‹จ login์„ ์ฆ‰์‹œ ๊ตฌํ˜„ํ•˜์—ฌ, status: 500 && code:'NOT_VALID' ์œผ๋กœ Promise์œผ๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

 

test('๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ', async () => {
    login.mockImplementation(() => {
      return new Promise((_, reject) => {
        reject({
          status: 500,
          code: 'NOT_VALID',
        });
      });
    });

    await wrapper.find('#id').setValue('chleorjs37@gmail.com');
    await wrapper.find('#password').setValue('testpassword');

    await wrapper.find('form').trigger('submit.prevent');

    expect(alert).toBeCalledWith('๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
  });

๋˜‘๊ฐ™์ด submit์„ ํŠธ๋ฆฌ๊ฑฐ ์‹œ์ผœ  toBeCalledWith์„ ์ด์šฉํ•˜์—ฌ alert์„ ํ˜ธ์ถœํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณธ๋‹ค.

 

ํ•˜์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค. ์ด๊ฒƒ์€ ์‹ค์ œ ์ฝ”๋“œ์—์„œ err ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•„์„œ ์ƒ๊ธด๋ฌธ์ œ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์ฝ”๋“œ์—์„œ๋„ err์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

 

 

//LoginPage.vue
// form์—์„œ submitํ•˜๋Š” ํ•จ์ˆ˜
const submit = () => {
      if (!form.id || !form.password) {
        alert('id๋˜๋Š” pw๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
        return false;
      }

      login({
        UserId: form.id,
        PassWord: form.password,
      })
        .then(value => {
          const { status, data } = value;
          if (status === 201) {
            alert('๋กœ๊ทธ์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
            router.push('/main');
          }
        })
        .catch(err => {
          const { status, code } = err;
     	  //์—๋Ÿฌ์— ๋Œ€ํ•œ ์ถ”๊ฐ€๋œ ๋ถ€๋ถ„
          if (status === 500 && code === 'NOT_VALID') {
            alert('๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
          }
        });
    };

 

๋กœ๊ทธ์ธ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ PASS๊ฐ€ ๋˜์—ˆ๋‹ค.

 

์ด๋กœ์„œ 4๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ค‘ ์„ธ๊ฐ€์ง€๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

  • id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.
  • id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™
  • ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

๐Ÿš  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 4 : ๋กœ๊ทธ์ธ API์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

๋‹จ์ˆœํžˆ ์„œ๋ฒ„์—์„œ 500์„ returnํ•ด์ค„ ๊ฒฝ์šฐ ์„œ๋ฒ„์—์„œ ์—๋Ÿฌ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ„์ฃผํ•œ๋‹ค.

//loginpage.test.js
test('๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ', async () => {
    login.mockImplementation(() => {
      return new Promise((_, reject) => {
        reject({
          status: 500,
        });
      });
    });
    await wrapper.find('#id').setValue('chleorjs37@gmail.com');
    await wrapper.find('#password').setValue('testpassword');

    await wrapper.find('form').trigger('submit.prevent');

    expect(alert).toBeCalledWith('์„œ๋ฒ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
  });
//LoginPage.vue
 const submit = () => {
 	login({
        UserId: form.id,
        PassWord: form.password,
      })
        .then(value => {
          const { status, data } = value;
          if (status === 201) {
            alert('๋กœ๊ทธ์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
            router.push('/main');
          }
        })
        .catch(err => {
          const { status, code } = err;
          if (status === 500 && code === 'NOT_VALID') {
            alert('๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
          } else if (status === 500) {
          //์ถ”๊ฐ€๋œ ๋ถ€๋ถ„
            alert('์„œ๋ฒ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
          }
        });
 }

 

submitํ•จ์ˆ˜์—์„œ alertํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์˜€๋Š”์ง€ toBeCalledWith๋ฅผ ํ†ตํ•ด์„œ ํ™•์ธํ•˜์˜€๊ณ  , PASS๊ฐ€ ๋œฌ์ง€ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋กœ์„œ 4๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๋ชจ๋‘ ํ•ด๊ฒฐํ•˜์˜€๋‹ค.

  • id/pw ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ alert์„ ๋„์šด๋‹ค.
  • id/pw ์ž…๋ ฅ ํ›„ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚  ๊ฒฝ์šฐ main์œผ๋กœ ์ด๋™
  • ๋กœ๊ทธ์ธ์‹œ ์ •๋ณด๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ๋กœ๊ทธ์ธ api์—์„œ ์„œ๋ฒ„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ ๊ฒฝ์šฐ

 

 

 

๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„์„ jest๋ฅผ ์ด์šฉํ•ด๋ณด์•˜๋‹ค. ๊ฐ„๋‹จํ•œ ์ž‘์—…๊ฐ™์ง€๋งŒ, mock ํ•˜๋Š” ๊ฒƒ๋ถ€ํ„ฐ , ํ˜ธ์ถœ์—ฌ๋ถ€ ํ™•์ธ ๊นŒ์ง€ ์ƒ๊ฐ๋ณด๋‹ค ํ•œ๊ฑธ์Œํ•œ๊ฑธ์Œ ์‰ฝ์ง€๋Š” ์•Š์•˜๋‹ค. ์‹ค๋ฌด์—์„œ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ‰์†Œ์— ์—ฐ์Šต์„ ๋งŽ์ด ํ•˜๋„๋ก ํ•˜์ž.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€