비동기 코드 테스트

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이 호출되기 전까지는 jesttest를 끝내지 않고 기다리게 됩니다.

이 코드에서는 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 를 리턴해주면, jestresolve될 때까지 기다려 줍니다.

이제는 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)

results matching ""

    No results matching ""

    99 other / uml

    04 react / JSX