[Jest] Testing Asynchronous Code

2023. 3. 1. 21:08공부 중/Node.js

 

0. 참고자료

 

 

 

Testing Asynchronous Code · Jest

It's common in JavaScript for code to run asynchronously. When you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest has several ways to handle this.

jestjs.io

 

 


1. 비동기 코드 테스트

 

javascript를 사용하면 비동기적인 코드를 작성하는 경우가 많고 이를 권장한다.

 

그렇기에 당연하게도 jest에서도 비동기 코드를 테스트하는 방법을 제공한다.

 

가. 그냥 하면 안 돼?

 

본격적으로 비동기 코드를 테스트하는 방법에 대해서 배우기 전에 기존에 동시적으로 동작하는 코드를 테스트하듯 비동기 코드를 테스트하면 어떻게 되는지 알아보자.

 

3초 후에 이름(엄준식)을 반환하는 함수를 만든다고 가정하자.

 

// asyncFn.js

const asyncFn={
    getName : () => {
        const name = '엄준식';
        setTimeout(()=>{
            return name;}
            , 3000
        );
    }
};
module.exports = asyncFn;    

지금까지 배운 방식으로 테스트 코드를 작성한다면 다음과 같을 것이다.

// asyncFn.test.js

const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    expect(aFn.getName()).toBe('엄준식');
});
> jest asyncFn.test.js

 FAIL  ./asyncFn.test.js
  ✕ 어떻게 사람 이름이 ... (2 ms)

  ● 어떻게 사람 이름이 ...

    expect(received).toBe(expected) // Object.is equality

    Expected: "엄준식"
    Received: undefined

      2 |
      3 | test('어떻게 사람 이름이 ...', ()=>{
    > 4 |       expect(aFn.getName()).toBe('엄준식');
        |                             ^
      5 | });
      6 |

      at Object.toBe (asyncFn.test.js:4:24)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.16 s
Ran all test suites matching /asyncFn.test.js/i.
Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

 

test fail이 발생하고 아래 메시지를 잘 살펴보면

  • Jest did not exit one second after the test run has completed. : 테스트를 시작하고 1초가 지난 시점에서도 jest는 나가지 않았다고 한다. 즉 1초 동안 반응이 없었고 jest는 가차 없이 fail로 처리했다.
  • This usually means that there are asynchronous operations that weren't stopped in your tests. : 이땐 보통 아직 실행 중인 비동기 작업이 있다는 소리다.

 

그렇다 우리의 jest 선생님은 기본적으로 우리가 비동기 작업을 완료할 때까지 하염없이 기다려주지 않는다.

 

jest가 비동기 코드를 테스트하는 과정에서 가장 중요한 것은 jest가 언제 비동기 코드가 실행을 완료하는지 아는 것이다.

 

언제 실행을 마치는지 알아야 대기 중인 다른 테스트를 수행할 수 있다.

 

jest에서 비동기 코드를 테스트하는 몇 가지 방법에 대해서 배워보자.

 


2. Promise

 

jest한테 비동기 코드를 테스트하고 있다고 미리 알려주면 된다.

 

이 다음을 이해하기 위해서는 javascript의 Promise가 무엇인지 알고 있어야 한다.

 

javascript에서 비동기를 간편히 처리하게 도와주는 Object다.

 

 

Promise - JavaScript | MDN

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

developer.mozilla.org

 

 

 

[Javascript] Promise

1. Promise Jest를 공부하면서 비동기 처리와 관련한 내용이 나왔다. 비동기 처리를 편하게 도와주는 Promise 객체에 대해서 배웠고 활용해 보았다. Promise를 활용하는 것에 있어서 중요한 포인트는 2가

ramen4598.tistory.com

 

 

Promise는 비동기 작업의 성공(resolve) 또는 실패(reject)와 그 결과 값을 나타내는 객체다.

 

jest에서는 비동기 코드의 테스트를 수행을 위해서 Promise를 이용한다.

 

Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will fail.

 

 

예시를 보면서 설명하자면 …

// asyncFn.test.js

const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    expect(aFn.getName()).toBe('엄준식');
});

저기 저 test(...) 뭐시기 안에서 Promise를 return하면 된다.

 

이렇게 고쳐보자.

 

// asyncFn.test.js

const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    return [Promise instance인 것]
});

이런 형식으로 작성하면 우리의 jest 성님이 기꺼이 비동기 작업이 끝나기를 기다려 주신다.

 

Promise 객체가 ‘엄준식'이라는 비동기 작업의 성공한 결괏값을 가지고 있어야 한다.

 

 

Promise.resolve() - JavaScript | MDN

Promise.resolve(value) 메서드는 주어진 값으로 이행하는 Promise.then 객체를 반환합니다. 그 값이 프로미스인 경우, 해당 프로미스가 반환됩니다. 그 값이 thenable(예, "then" 메소드 가 있음)인 경우, 반환

developer.mozilla.org

 

Promise.resolve(value) 메서드는 주어진 값으로 이행하는 Promise.then 객체를 반환합니다.


// 성공한 경우 반환
const promise1 = new Promise((resolve, reject) => {
  resolve('Success!');
});

// 결과값에 접근
promise1.then((value) => {
  console.log(value);
  // Expected output: "Success!"
});

따라서 asyncFn.js에서 저 '엄준식'을 가진 Promise.then를 반환해야 한다.

 

// asyncFn.js

const asyncFn={
    getName : () => {
        const name = '엄준식';
        return new Promise((resolve, reject) => {
            setTimeout(()=>{resolve(name);}, 3000);
        });
    }
};
module.exports = asyncFn;
// asyncFn.test.js

const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    return aFn.getName().then((name)=>{
        expect(name).toBe('엄준식');
    });
});

이렇게 끝내도 되지만 예외처리를 위해서 Promise.reject에 대해서 알아보자면 …

 

 

Promise.reject() - JavaScript | MDN

Promise.reject(reason) 메서드는 주어진 이유(reason)로 거부된 Promise 객체를 반환합니다.

developer.mozilla.org

 

 

Promise.reject(reason) 메서드는 주어진 이유(reason)로 거부된 Promise 객체를 반환합니다. … reasonError생성자의 인스턴스로 만들면 유용합니다.

const asyncFn={
    getName : () => {
        const name = '엄준식';
        return new Promise((resolve, reject) => {
            try {
                setTimeout(()=>{resolve(name);}, 3000);
            } catch (error) {
                reject(new Error('Error!'));
            }
        });
    }
};
module.exports = asyncFn;
const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    return aFn.getName().then((name)=>{
        expect(name).toBe('엄준식');
    },(reason)=>{
        expect(reason).toBe('Error!');
    });
});

 

 

가. resolves, rejects Matcher

 

.resolves.rejects라는 Matcher가 있다.

  • .resolves : promise가 resolve되고 또한 그 값이 예상과 같을지 테스트한다. reject되거나 예상값과 다르다면 fail한다.
  • .rejects : promise가 reject되고 또한 에러 메시지가 예상과 같을지 테스트한다. resolve되거나 예상 에러메시지와 다르다면 fail한다.

 

const aFn = require('./asyncFn');

test('어떻게 사람 이름이 ...', ()=>{
    return expect(aFn.getName()).resolves.toBe('엄준식');
});

test('어떻게 사람 이름이 ...', ()=>{
    return expect(aFn.getName()).rejects.toMatch('Error!');
});

 


3. Async/Await

 

asyncawait를 이용해서 테스트하는 방법도 있다.

 

 

[Javascript] async & await

0. 참고자료 1. async async function - JavaScript | MDN async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의합니다. 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로,

ramen4598.tistory.com

 

이번에는 ‘엄’를 반환받고자 한다.

 

// asyncFn.js
const asyncFn = {
	getUm : ()=>{
    	const Um = '엄';
		return new Promise((resolve, reject)=> {
			setTimeout(()=>{resolve(Um);}, 3000);
        });
    },
};
module.exports = asyncFn;

asyncFn.js 에서 Promise 객체를 생성하고 반환하는 것은 동일하다.

 

// asyncFn.js
const aFn = require('./asyncFn');

test('어...어..어..ㅁ..', async()=> {
    const data = await aFn.getUm();
    expect(data).toBe('엄');
    expect(data).not.toBe('준');
    expect(data).not.toBe('식');
});    
  • async function 비동기함수2(){ const result = await 비동기함수1; …}
    : async를 이용해서 비동기 함수를 기다리는 비동기 함수를 선언할 수 있다.

 

 

async function - JavaScript | MDN

async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의합니다. 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환

developer.mozilla.org

 


4. Callback

 

곧 죽어도 Promise는 못 쓰겠다면 다른 방법이 있다.

 

그냥 비동기 함수가 끝난지 따로 알려주는 것이다.

 

이 방법을 사용하기 위해서는 테스트할 함수가 콜백함수를 인자로 받도록 작성해야 한다. (최대 단점)

 

// asyncFn.js

const asyncFn={
    ...
    getJun : (callback)=>{   // 굳이 이렇게 콜백을 인수로 전달받게 만들어야함.
        const Jun = '준';
        setTimeout(()=>{
            callback(Jun);
            }, 3000
        );
    },
};
module.exports = asyncFn;
// asyncFn.test.js

const aFn = require('./asyncFn');
...
test('엄.준.', (done)=>{   // done이라는 콜백함수 전달
    function callback(Jun) {   // aFn.getUm에 전달할 콜백함수
        expect(Jun).toBe('준');
                done();   // 비동기 처리 완료됨을 test에게 알려줌.
    }
    aFn.getJun(callback);  // expect절을 포함한 콜백함수를 전달하면서 실행
});
  • test('엄.준.', (done)=>{...} : test()done라는 콜백함수가 호출되기 전까진 종료되지 않는다.

 

참고로 굳이 done 이라고 정해진 것은 아니다.

 

end라던가 ddong라던가 아무거나 상관없다.

 

done을 호출되지 않으면 fail이다.

 

위의 코드에서 만약에 expect(Jun).toBe('준'); 부분에서 fail이 발생되면 어떻게 될까?

 

done()이 실행되지 않는다.

 

thrown: "Exceeded timeout of 5000 ms for a test while waiting for `done()` to be called.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

 

그러면서 timeout이 발생된다.

 

따라서 정상적으로 테스트하기 위해서는 try-catch문을 활용해서 done()을 실행시키자.

 

test('엄.준.', (done)=>{
    function callback(Jun) {
        try{
            expect(Jun).toBe('준');
            done();
        }catch(error){
            done(error);
        }
    }
    aFn.getJun(callback);
});

 


'공부 중 > Node.js' 카테고리의 다른 글

[Nodejs] TypeError: Cannot convert undefined or null to object  (0) 2023.03.07
[Jest] Setup and Teardown  (0) 2023.03.01
[Jest] Matcher  (0) 2023.03.01
[Jest] node.js 테스트 프레임워크  (0) 2023.03.01
[Javascript] Promise  (0) 2023.03.01