[jest]

lim1313ยท2022๋…„ 3์›” 29์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
22/22

๐Ÿ‰ jest ์‹œ์ž‘ํ•˜๊ธฐ

npm init --yes
npm i jest --global
jest --init
npm i --save-dev jest
npm i @types/jest

jest --coverage : ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ

๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ๋ชจ๋“  test ์‹คํ–‰

  "scripts": {
    "test": "jest --watchAll" 
  },

commit๋˜์ง€ ์•Š์€ ํŒŒ์ผ๋งŒ test ์‹คํ–‰

git init
.gitignore์— 'node_modules/*' ์ถ”๊ฐ€ 
git add .
git commit
  "scripts": {
    "test": "jest --watch" // 
  },    

๐Ÿ‰ ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

  • ์˜ˆ์ œ) calculator.js
class Calculator {
  constructor() {
    this.value = 0;
  }

  set(num) {
    this.value = num;
  }

  clear() {
    this.value = 0;
  }

  add(num) {
    const sum = this.value + num;
    if (sum > 100) {
      throw new Error('Value can not be greater than 100');
    }
    this.value = sum;
  }

  subtract(num) {
    this.value = this.value - num;
  }

  multiply(num) {
    this.value = this.value * num;
  }

  divide(num) {
    this.value = this.value / num;
  }
}

module.exports = Calculator;
  • describe : ๊ด€๋ จ์žˆ๋Š” ํ…Œ์ŠคํŠธ๋ผ๋ฆฌ ๋ฌถ์Œ.
  • beforeEach : ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ•ด์˜ค๋””๊ธฐ ์ „์— ์‹คํ–‰
    (๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ์ด์ „์˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ์ดํ›„์˜ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฉด ์•ˆ๋œ๋‹ค.)
    beforeEach, afterEach, beforeAll, afterAll => jest ๊ณต์‹
describe('Calculator', () => {
  let cal;
  // ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ƒˆ๋กœ์šด object๋ฅผ ์ƒ์„ฑ
  beforeEach(() => {
    cal = new Calculator();
  });

  it('inits with 0', () => {
    expect(cal.value).toBe(0);
  });
  
  it('subtract', () => {
    cal.set(1);
    cal.subtract(2);
    expect(cal.value).toBe(-1);
  });

   describe('divides', () => {
    it('0 divide 0', () => {
      cal.divide(0);
      expect(cal.value).toBe(NaN);
    });

    it('1 divide 0', () => {
      cal.set(1);
      cal.divide(0);
      expect(cal.value).toBe(Infinity);
    });
  });
});

๐Ÿ‰ ์—๋Ÿฌ ํ…Œ์ŠคํŠธ

  • expect().toThrow() : ์—๋Ÿฌ ํ…Œ์ŠคํŠธ
 it('add should throw an error if value is greater than 100', () => {
    expect(() => {
      cal.add(101);
    }).toThrow('Value can not be greater than 100');
  });

๐Ÿ‰ ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ

const fetchProduct = require('../async.js');

describe('Asnyc', () => {
  /* done ๋ฐฉ์‹ */
  it('aysnc-done', (done) => {
    fetchProduct().then((item) => {
      expect(item).toEqual({ item: 'Mike', price: 200 });
      done(); //=> jest๊ฐ€ ๋๋‚˜๋Š” ์‹œ์  ์ง€์ • (5์ดˆ ์ •๋„ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰์ด ๋Š๋ฆผ)
    });
  });
  
  /* async return ๋ฐฉ์‹ */
  it('aysnc-return', () => {
    return fetchProduct().then((item) => {
      expect(item).toEqual({ item: 'Mike', price: 200 });
    });
  });

  it('aysnc-return-error', () => {
    return fetchProduct().catch((item) => {
      expect(item).toEqual('network error');
    });
  });

  /* async await ๋ฐฉ์‹ */
  it('aysnc-await', async () => {
    const product = await fetchProduct();
    expect(product).toEqual({ item: 'Mike', price: 200 });
  });

  it('aysnc-await-error', async () => {
    try {
      await fetchProduct('error');
    } catch (error) {
      expect(error).toBe('network error');
    }
  });

  /* async resolves, rejects ๋ฐฉ์‹ */
  it('aysnc-resolves', () => {
    return expect(fetchProduct()).resolves.toEqual({
      item: 'Mike',
      price: 200,
    });
  });

  it('aysnc-rejects', () => {
    return expect(fetchProduct('error')).rejects.toBe('network error');
  });
});

๐Ÿ‰ Mock ํ•จ์ˆ˜

mock ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์‹ค์ œ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ , ๊ฐ„๋‹จํ•˜๊ฒŒ test ์ง„ํ–‰ ๊ฐ€๋Šฅํ•˜๋‹ค.

  • check.js
function check(predicate, onSuccess, onFail) {
  if (predicate()) {
    onSuccess('yes');
  } else {
    onFail('no');
  }
}

module.exports = check;
  • check.test.js
const check = require('../check.js');

describe('check', () => {
  let onSuccess;
  let onFail;

  beforeEach(() => {
    onSuccess = jest.fn(); //-> ๊ฐ€์งœ ํ•จ์ˆ˜ ์ƒ์„ฑ
    onFail = jest.fn(); //-> ๊ฐ€์งœ ํ•จ์ˆ˜ ์ƒ์„ฑ
  });

  it('should call onSuccess when predicate is true', () => {
    check(() => true, onSuccess, onFail);

    // expect(onSuccess.mock.calls.length).toBe(1);
    // expect(onSuccess.mock.calls[0][0]).toBe(1);
    // expect(onFail.mock.calls.length).toBe(0);

    expect(onSuccess).toHaveBeenCalledTimes(1);
    expect(onSuccess).toHaveBeenCalledWith('yes');
    expect(onFail).toHaveBeenCalledTimes(0);
  });

  it('should call onFail when predicate is false', () => {
    check(() => false, onSuccess, onFail);
    expect(onFail).toHaveBeenCalledTimes(1);
    expect(onFail).toHaveBeenCalledWith('no');
    expect(onSuccess).toHaveBeenCalledTimes(0);
  });
});

๐Ÿ‰ ์ œํ’ˆ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

  • product_client.js
class ProductClient {
  fetchItems() {
    return fetch('http://example.com/login/id+password').then((res) =>
      res.json()
    );
  }
}

module.exports = ProductClient;
  • product_service_no_di.js
const ProductClient = require('./product_client');

class ProductService {
  constructor() {
    this.ProductClient = new ProductClient();
  }

  fetchAvailableItems() {
    return this.ProductClient.fetchItems().then((items) =>
      items.filter((item) => item.available)
    );
  }
}

module.exports = ProductService;

โš ๏ธ mock ์ด์šฉ (mock ๋‚จ์šฉ)

  • ์—ฌ๊ธฐ์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒƒ์€ product_service_no_di์ด๊ธฐ ๋•Œ๋ฌธ์—, ๋‹ค๋ฅธ ์–ด๋–ค ์˜์กด์„ฑ์— ๋Œ€ํ•ด์„œ๋Š” mock์„ ์ด์šฉํ•œ๋‹ค.
  • ๋‹ค๋ฅธ ๋‹จ์œ„ ๊ฐ„์˜ ์˜์กด์„ฑ์ด ์กด์žฌํ•˜๋ฉด mock์„ ์ด์šฉํ•˜์—ฌ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.
const ProductService = require('../product_service_no_di.js');
const ProductClient = require('../product_client.js');

// product_client์€ mock์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๋ช…์‹œ
jest.mock('../product_client');

describe('product service', () => {
  // fetchItems์— ๋Œ€ํ•ด์„œ mock ํ•จ์ˆ˜ ์ƒ์„ฑ
  const fetchItems = jest.fn(async () => [
    { item: '๐Ÿฅ›', available: true },
    { item: '๐ŸŒ', available: false },
  ]);

  //product_client์™€ fetchItems ํ•จ์ˆ˜ ์—ฐ๊ฒฐ
  ProductClient.mockImplementation(() => {
    return {
      fetchItems,
    };
  });

  let productService;

  beforeEach(() => {
    productService = new ProductService();
    //=> network ์ƒํƒœ์— ์˜์กดํ•˜๋Š” ์ฝ”๋“œ๋Š” ์ข‹์ง€ ์•Š๋‹ค.
    // ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด product_client์„ mockํ•œ๋‹ค.
  });

  it('should filter out only available items', async () => {
    const items = await productService.fetchAvailableItems();
    expect(items.length).toBe(1);
    expect(items).toEqual([{ item: '๐Ÿฅ›', available: true }]);
  });
});

โš ๏ธ stub ์ด์šฉ


profile
start coding

0๊ฐœ์˜ ๋Œ“๊ธ€