4주 프로젝트 DAY 9

  • jest 테스트 시작할때 koa app을 붙이고 싶다.
  • jest 테스트에서 koa app을 붙였다. 다만 더 알아봐야한다.
  • Nodejs 내부 구조, 동작 원리가 궁금했다!
  • jest 테스트를 할때 각 테스트 할때마다 서버 올리고 테스트 끝나면 내리고 싶어요.

jest 테스트 시작할때 koa app을 붙이고 싶다.

에러1

import app from "../index";
import { Server } from "http";
import supertest from "supertest";

describe(`supertest`, () => {

  let server: Server;
  let request: supertest.SuperTest<supertest.Test>;
  beforeEach(() => {
    request = supertest(app);
  });

  it(`should upload file and get FileMetadatalist from server and check it in downloads files`, async () => {

    const res = await request.get("http://localhost:4002/fileMetadataList");
    console.log(res);
  });
});

image.png

에러2

import app from "../index";
import { Server } from "http";
import supertest from "supertest";

describe(`supertest`, () => {

  let server: Server;
  let request: supertest.SuperTest<supertest.Test>;
  beforeEach(() => {
    server = app.listen(4002);
    request = supertest(server);
  });

  it(`should upload file and get FileMetadatalist from server and check it in downloads files`, async () => {

    const res = await request.get("http://localhost:4002/fileMetadataList");
    console.log(res);
  });
});

image.png

what is ECONNRESET ??

"ECONNRESET" means the other side of the TCP conversation abruptly closed its end of the connection. This is most probably due to one or more application protocol errors. You could look at the API server logs to see if it complains about something.

출처 : stackoverflow.com

에러3

koa server jest로 구글링
https://medium.com/scrum-ai/4-testing-koa-server-with-jest-week-5-8e980cd30527

in app.ts

import Koa from "koa";
import router from "./router";
import koaBody from "koa-body";

const app = new Koa();
const PORT: number = process.env.NODE_ENV === "production" ? 4001 : 4002;

app.use(koaBody({
  multipart: true,
  formidable: {
    uploadDir: __dirname + "/uploads",
  },
}));

app.use(router.routes());
app.use(router.allowedMethods());
app.use(async (ctx, next) => {
  ctx.status = 404;
});

app.on("error", (err, ctx) => {
  console.log(err);
  ctx.status = 500;
});

export default app;

in FileApiRouter.test.ts

import app from "../index";
import supertest from "supertest";

describe(`supertest`, () => {

  let request: supertest.SuperTest<supertest.Test>;
  beforeEach(() => {
    request = supertest(app.callback());
  });

  it(`should upload file and get FileMetadatalist from server and check it in downloads files`, async () => {

    const res = await request.get("http://localhost:4002/fileMetadataList");
    console.log(res);
  });
});

image.png

jest 테스트에서 koa app을 붙였다. 다만 더 알아봐야한다.

in index.ts

import Koa from "koa";
import router from "./router";
import koaBody from "koa-body";

const app = new Koa();
const PORT: number = process.env.NODE_ENV === "production" ? 4001 : 4002;

app.use(koaBody({
  multipart: true,
  formidable: {
    uploadDir: __dirname + "/uploads",
  },
}));

app.use(router.routes());
app.use(router.allowedMethods());
app.use(async (ctx, next) => {
  ctx.status = 404;
});

app.on("error", (err, ctx) => {
  console.log(err);
  ctx.status = 500;
});

// app.listen(PORT, () => {
//   console.log(`server is listening to port ${PORT}`);
// });

export default app;

in FileApiRouter.test.ts

import uuid from "uuid/v4";
import { uploadFile, downloadFile } from "./uploadAndDownloadFile.test";
import { getFileMetadataList } from "./uploadAndGetFileMetadataList.test";
import app from "../index";
import { Server } from "http";
import supertest from "supertest";

describe(`upload and download and listFileMetadata test from StrageClass`, () => {
  let server: Server;
  beforeAll(async () => {
    server = app.listen(4002, () => {
      console.log(`server is listening to port ${4002}`);
    });
  });

  afterAll(() => {
    server.close();
  });

  it(`should upload file and get FileMetadatalist from server and check it in downloads files`, async () => {
    const imageInBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
    const imageBuffer = Buffer.from(imageInBase64, "base64");
    const filename = uuid();

    await uploadFile(filename, imageBuffer);

    const fileMetadataList = await getFileMetadataList();
    const filenameFromApi = fileMetadataList.map((fileMetadata) => {
      return fileMetadata.filename;
    });

    const downloadFiles: string[] = await Promise.all(filenameFromApi.map(async (filenameToBeDownloaded: string) => {
      const downloadedFile = await downloadFile(filenameToBeDownloaded);
      const downloadedFileInBase64 = downloadedFile.toString("base64");
      return downloadedFileInBase64;
    }));

    expect(downloadFiles).toEqual(expect.arrayContaining([imageInBase64]));
  });
});

이 문제를 해결한 방법

image.png

ENOENT : 즉 파일이 없다!
그래서 <상위디렉토리>/src/ 에 uploads를 넣어주었다.

그 결과

image.png

이 문제가 발생하게 된 원인은 koa app이 현재 <상위디렉토리>/src 에 위치한다.

그리고 koa-body 라이브러리를 이용해서 formdata를 파일을 업로드 하고 있는 상황이다.

나는 현재 코딩을 외장하드에서 하고있다.. formidable 옵션을 넣어주지 않으면 fs.renamefile에서 cross device 어쩌구 에러가 발생한다.

아무튼 koa-body 라이브러리 이용해서 formdata로 파일을 업로드하는데

in index.ts

app.use(koaBody({
  multipart: true,
  formidable: {
    uploadDir: __dirname + "/uploads",
  },
}));

__dirname : 현재 디렉토리. 즉, <상위디렉토리>/src/이다.
여기에 uploads 디렉토리에 아래에 파일을 업로드 하라는 말이다.

그렇다! 여기다가 업로드 하라 해놓고 디렉토리도 안 만들어놓으니까 파일을 업로드도 못했다!

여기서 나의 삽질의 이유를 찾기 위해 내가 해왔던 것들을 하나 하나 되짚어봤다.
in package.json

  "scripts": {
    ...
    "watch-start": "concurrently --kill-others \"tsc --watch\" \"nodemon dist/index.js\"",
    ...
  },

그렇다!!! tsc! 트랜스파일을 했다!
그렇다면 tsconfig.json을 봐야한다. 왜냐하면 tsconfig.json은 타입스크립트를 트랜스파일할때 적용하는 설정들이기 때문이다.

{
  "compilerOptions": {
    ...
    "outDir": "dist",
    ...
}

트랜스파일된 js파일들이 /dist로 가게 되어있다..
tsconfig.json이 /server디렉토리에 있기 때문에 /server/dist라는 디렉토리를 만들어 놓으면 (없으면 만드나? 이건 찾아봐야한다..) 그 곳에 index.js가 있지 않나?

in index.js

app.use(koa_body_1.default({
    multipart: true,
    formidable: {
        uploadDir: __dirname + "/uploads",
    },
}));

/server/dist/index.js 코드내에서
__dirname 는? /server/dist 이지 않는가!?
그렇다.

그동안 npm run watch-start 그리고 npm run test할때는
/server/dist 에 파일이 저장되고 읽어왔다.

in index.ts

app.use(koa_body_1.default({
    multipart: true,
    formidable: {
        uploadDir: __dirname + "/uploads",
    },
}));

그런데 /server/dist/src/에서 __dirname 이 /server/dist/src 이다.

index.ts의 위치가 달라서 __dirname도 달라졌다. 그래서 test할때에는 다른 위치에 저장된 것이다.

정리 jest test 내에서만 koa app.listen()을 통해 서버를 올려두고, 테스트한 후에 서버를 내릴 수 있다.

Nodejs 내부 구조, 동작 원리가 궁금했다!

정말 좋은 그림을 찾아냈다.
출처 : 거너 호프만 알고리즘

image.png

매우매우 좋은 레퍼런스
출처: 빨간색코딩

또 다른 유명한 레퍼런스
출처: 조대협의 블로그

또 좋은 레퍼런스
출처: [NodeJS] nodejs는 single-thread가 아니다

jest 테스트를 할때 각 테스트 할때마다 서버 올리고 테스트 끝나면 내리고 싶어요.

결과를 미리 얘기를 하면
jest의 beforeEach, afterEach라는 메서드를 이용해서 서버를 올렸다 내렸다. 하면서 테스트를 돌릴 수 있다.

내가 겪은 상황을 순차적으로 정리하면,

  1. beforeAll을 통해 서버 올리고 테스트 파일 하나는 동작해요. afterall에서 서버 내립니다.

  2. 테스트파일이 여러개 일때는
    listen EADDRINUSE: address already in use ::: 이런 에러가 뜬다.. 뭔가!
    레퍼런스

나만 뜨는 에러가 아니다~
이 사람은 jest에 --runInBand옵션을 넣으래요~
여러 테스트 파일이 정상적으로 테스트가 돼요.

그런데?
--runInBand 이 옵션이 뭐냐~
출처: jest 공홈
기본은 jest가 테스트를 병렬로 돌립니다~ spawning process 어쩌구 나오죠. 프로세스 복제해서 여러 테스트를 한번에 돌린다는 거죠.
--runInBand는 순차적으로 돌게끔 하는 옵션입니다.
그러면 테스트파일이 오지게 많을때는 그 테스트 다 돌때까지 시간이 걸리겠죠.

  1. beforeEach, afterEach를 사용해봤습니다. 잘 동작해요. --runInBand 옵션 없이도요.
    beforeAll -> beforeEach !!
    출처: jest 공홈

beforeEach: Repeating Setup For Many Tests
예를 들어, DB에 여러 테스트가 상호작용한다고 가정해봐요.
각 테스트마다 DB와 연결을 설립해야되고, 테스트 끝날때마다 DB와 연결을 끊어야해요.

beforeEach(() => {
  initializeCityDatabase();
});

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

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

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

이러한 코드가 가능하답니다.

beforeAll: One-Time Setup
한번만 해도 되는거래요.
정확한 이유는 아직 찾지 못했지만
db나 server에 네트워크 요청 응답을 위한 테스트는
beforeEach, afterEach를 통해 연결 수립, 끊음을 해주면 된다~ 입니다.