2020-3-14 Jest(2)

Testing asynchronous code

  • js코드를 비동기적으로 실행하는것은 최근 공통된 일이다.

callbacks

  • 많이 사용되는 비동기 패턴은 콜백. fetchData(callback)는 callback(data)를 fetch한다.
// Don't do this!
test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter');
  }

  fetchData(callback);
});

// Right thing
test('the data is peanut butter', done => {
  function callback(data) {
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }

  fetchData(callback);
});
  • 위에 있는 예시로 하면 callback이 실행되기전에 fetchData가 끝난다.
  • test인자에 done을 전달한다. Jest는 done이 callback될때까지 test를 끝내지 않고 기다린다 !!!
  • done이 호출되지 않으면 테스트는 timeout error를 호출하면서 실패한다.
  • 테스트가 왜 실패됬는지 확인하기 위해 try-catch를 사용하고 catch문 안에서 done(error)를 쓰면된다.

Promise

  • promise를 사용하면 비동기 테스트를 직접적으로 다룰 수 있다. jest는 promise가 resolve될때까지 기다린다. promise가 reject되면 jest는 자동으로 실패가 된다.
test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});

test('the data is peanut butter', () => {
  return expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', () => {
  return expect(fetchData()).rejects.toMatch('error');
});
  • .resolve, .reject를 이용해서 기대값을 매칭시킬 수도 있다.

async/await

  • test함수에 async키워드를 붙여서 비동기 처리를 할 수 있다. 또한 async/await키워드에 .resolves, .reject를 조합할수도 있다.
test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

test('the data is peanut butter', async () => {
  await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  await expect(fetchData()).rejects.toThrow('error');
});

Setup and teardown

  • 테스트를 실행전에 설정을 반복적인 설정, 한번설정. 테스트가 끝나고 실행되는 환경을 구축할 수 있다.
  • beforeEach(), afterEach()의 매번 테스트가 실행될때마다 실행전후로 실행이된다.
  • 데이터베이스 테스트의 경우 사전에 connection이 되어야한다. 그리고 connectio들도 promise나 async/await을 사용하는 경우가 많기때문에 beforeEach()에서 설정을 하고 비동기처리의 경우 done을 활용한다.
beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

// other case
beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});
  • 테스트 시작전에 file을 읽어야되는 경우, beforeAll(), afterAll()을 활용해서 모든테스트가 동작하기 사전에 설정할수도 있다.

scoping

  • 기본적으로 before, after블록은 모든 테스트파일에 적용된다. 뿐만 아니라 describe로 테스트의 그룹화 역시할 수 있다. describe를 사용해서 테스트 그룹화를 하면 after, before는 해당 describe에서만 사용할 수 있다 !!
// Applies to all tests in this file
beforeEach(() => {
  return initializeCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

describe('matching cities to foods', () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 sausage', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});
  • 바깥의 beforeEach의 경우 describe안에 있는 beforeEach가 실행되기전에 실행된다. beforeAll()의 경우 beforeEach()보다 먼저 실행이 된다.

order of execution of describe and test blocks

  • jest는 실제 test()를 실행하기 전에 모든 describe handler를 실행한다. 이것은 환경설정을 decribe블록안 대신 before* after*에 해야되는 이유.
  • describe 모두 실행되면 jest는 순차적으로 모든 테스트를 실행한다.
describe('outer', () => {
  console.log('describe outer-a');

  describe('describe inner 1', () => {
    console.log('describe inner 1');
    test('test 1', () => {
      console.log('test for describe inner 1');
      expect(true).toEqual(true);
    });
  });

  console.log('describe outer-b');

  test('test 1', () => {
    console.log('test for describe outer');
    expect(true).toEqual(true);
  });

  describe('describe inner 2', () => {
    console.log('describe inner 2');
    test('test for describe inner 2', () => {
      console.log('test for describe inner 2');
      expect(false).toEqual(false);
    });
  });

  console.log('describe outer-c');
});

// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test for describe inner 1
// test for describe outer
// test for describe inner 2
  • 위의 코드를 보면 테스트가 실행되기전에 순차적으로 describe안에있는 console.log들이 실행되는것을 확인할 수 있다.

일반적인 조언

test.only('this will be the only test that runs', () => {
  expect(true).toBe(false);
});

test('this test will not run', () => {
  expect('A').toBe('A');
});
  • Test.only를 통해 테스트를 딱 한번만 실행되게 할 수 있다.
Written on March 14, 2020