비동기 코드 테스트
2022-05-15
callback 패턴
비정상 동작
먼저 3초후 이름을 알려주는 함수를 작성할게요.
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
callback(name);
}, 3000)
}
};
module.exports = fn;
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", () => {
function callback(name) {
expect(name).toBe("Mike");
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 받아온 이름은 Mike (1ms),
v 0 + 1 은 1이야 (1ms),
둘다 통과는 하긴 했는데, 이상합니다.
3초 후에 받아온 이름은 Mike야
의 경우 1ms가 걸렸습니다.
Tom 으로 변경하여 실패하는 코드로 변경해 보겠습니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", () => {
function callback(name) {
expect(name).toBe("Tom");
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 받아온 이름은 Mike (1ms),
v 0 + 1 은 1이야 (1ms),
그래도 바로 통과 했습니다.
가장 많이 사용하는 비동기 패턴은 callback
을 사용하는 패턴일 거에요.
그런데 jest는 실행이 끝에 도달하게 되면 바로 끝납니다.
기다리지 않고 그냥 끝내는 겁니다.
done() 을 이용하여 정상 동작
이럴때는 test
함수에 done
이라는 callback
함수를 전달해 주면 됩니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
expect(name).toBe("Tom");
done();
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후에 받아온 이름은 Mike (3023ms),
v 0 + 1 은 1이야 (1ms),
Expected: "Tome"
Received: "Mike"
done
이 호출되기 전까지는 jest
는 test
를 끝내지 않고 기다리게 됩니다.
이 코드에서는 callback
실행 후 done
이 호출 되도록 변경하면 됩니다.
테스트를 돌려보면 위와 같이 실패를 하게 됩니다.
이름이 달라서 그런거구요. 이름을 변경한뒤 다시 테스트를 해보겠습니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
expect(name).toBe("Tom");
done();
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
x 3초 후에 받아온 이름은 Mike (3006ms),
v 0 + 1 은 1이야 (1ms),
3초 정도가 걸렸고, 테스트는 통과 하였습니다.
done() 이 호출 되지 않는다면?
만약 done
을 전달받았는데, done
이 호출되지 않으면 테스트는 실패하게 됩니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
expect(name).toBe("Tom");
//done();
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후에 받아온 이름은 Mike (5007ms),
v 0 + 1 은 1이야 (1ms),
: Timeout - Async callback ...
자세히 살펴보면 타임아웃이 뜬걸 알 수 있고,
5초 동안 타임아웃이 있고, 그 동안 응답이 없으면 실패가 뜨게 됩니다.
done() 이 호출 되지 않았을때, 예외 처리 : try catch
api 의 에러를 감지하고 싶다면 try catch
문으로 감싸주면 됩니다.
throw new Error()
로 에러를 한번 발생 시켜 볼께요.
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
//callback(name);
throw new Error('서버 에러..');
}, 3000)
}
};
module.exports = fn;
test
를 해보면 에러가 찍히는걸 알수 있습니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
try {
expect(name).toBe("Tom");
done();
} catch (error) {
done();
}
}
fn.getNmae(callback);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후에 받아온 이름은 Mike (5007ms),
v 0 + 1 은 1이야 (1ms),
서 버 에 러 ..
callback
패턴은 이렇게 테스트를 하시면 됩니다.
promise
비정상 promise
이번에는 callback
이 아닌 promise
를 사용해 보겠습니다.
사실 promise
를 사용하는게 좀더 간결합니다.
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
//callback(name);
throw new Error('서버 에러..');
}, 3000)
}
getAge : () => {
const age = 30;
return new Promise((res, rej) => {
setTimeout(() => {
res(age);
}, 3000);
});
}
};
module.exports = fn;
promise
를 리턴해주면, jest
는 resolve
될 때까지 기다려 줍니다.
이제는 done
을 넘겨주지 않아도 됩니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 나이는 30", () => {
fn.getAge().then(age => {
expect(age).toBe(30);
});
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 받아온 나이는 30,
v 0 + 1 은 1이야 (1ms),
둘다 결과는 통과했지만, 굉장히 빠르게 결과가 나왔습니다.
뭔가 이상합니다.
31로 변경해 보겠습니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 나이는 30", () => {
fn.getAge().then(age => {
expect(age).toBe(31);
});
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 받아온 나이는 30,
v 0 + 1 은 1이야 (1ms),
그래도 통과되는걸 봐서 코드가 잘못된걸 알 수 있습니다.
return을 사용하여 정상화
promise
를 사용할때는 return
을 해줘야 합니다.
return
을 빼먹으면 그대로 종료 됩니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 나이는 30", () => {
return fn.getAge().then(age => {
expect(age).toBe(31);
});
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후에 받아온 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)
Expected: 31
Received: 30
return
넣은 후 테스트를 해보면,
3초가 걸린 후 정상적으로 실패했다고 알려줍니다.
then 보다 간결한 resolves()
보다 간단하게 작성을 하고 싶으면, matcher
를 사용하면 됩니다.
expect
를 바로 작성하고, resolves
를 사용해 줍니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 나이는 30", () => {
expect(fn.getAge()).resolves.toBe(30);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 받아온 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)
통과함을 알 수 있습니다.
실패 처리도 가능 : rejects()
만약 reject
를 테스트해주고 싶으시면,
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
//callback(name);
throw new Error('서버 에러..');
}, 3000)
}
getAge : () => {
const age = 30;
return new Promise((res, rej) => {
setTimeout(() => {
//res(age);
rej('error');
}, 3000);
});
}
};
module.exports = fn;
fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 나이는 30", () => {
expect(fn.getAge()).rejects.toBe(30);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후에 받아온 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)
Expected: 30
Received: "error"
테스트는 정상적으로 실패했음을 알 수 있습니다.
error
라는 메시지가 정상적으로 날아 왔는지 확인을 하기 위해서 아래와 같이 변경할 수도 있습니다.
fn.test.js
const fn = require("./fn");
test("3초 후에 에러가 납니다.", () => {
expect(fn.getAge()).rejects.toMatch('error');
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후에 에러가 납니다. (3007 ms)
v 0 + 1 은 1이야 (1ms)
resolves() 말고 async await 를 이용하기
fn 파일은 원래대로 돌려 놓습니다.
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
//callback(name);
throw new Error('서버 에러..');
}, 3000)
}
getAge : () => {
const age = 30;
return new Promise((res, rej) => {
setTimeout(() => {
res(age);
//rej('error');
}, 3000);
});
}
};
module.exports = fn;
사용하는 방식은 async
await
함수를 사용하는 것과 동일합니다.
fn.test.js
const fn = require("./fn");
test("3초 후 나이는 30", async () => {
const age = await fn.getAge();
expect(age).toBe(30);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)
잘 동작합니다.
실패하도록 변경해 보겠습니다.
fn.test.js
const fn = require("./fn");
test("3초 후 나이는 30", async () => {
const age = await fn.getAge();
expect(age).toBe(31);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
FAIL
x 3초 후 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)
Expected: 31
Received: 30
정상적으로 실패했음을 알수 있습니다.
resolves
matcher
사용으로도 변경이 가능합니다.
fn.test.js
const fn = require("./fn");
test("3초 후 나이는 30", async () => {
await expect(age).toBe(3);
});
test("0 + 1 은 1이야", () => {
expect(fn.add(0, 1)).toBe(1);
});
PASS
v 3초 후 나이는 30 (3007 ms)
v 0 + 1 은 1이야 (1ms)