일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- type challenge
- Algorithm
- 타입스크립트
- 문자열
- javascript
- 소켓
- 백준
- 크롤링
- 자바스크립트
- socket
- Node.js
- 타입 챌린지
- 프로그래머스
- typescript
- 수학
- TCP
- HTTP 완벽 가이드
- HTTP
- BFS
- Crawling
- 레벨 1
- 그래프
- ip
- 알고리즘
- 쉬운 문제
- 프로그래머스 레벨 2
- 가천대
- dp
- dfs
- Nestjs
- Today
- Total
kakasoo
Nestia를 활용한 e2e 테스트 본문
이번에 테스팅할 API는?
import { TypedBody, TypedRoute } from '@nestia/core';
import { Controller } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { createResponseForm, ResponseForm } from '../interceptors/transform.interceptor';
import { CreateUserDto } from '../models/dtos/create-user.dto';
import { DecodedUserToken } from '../models/tables/user.entity';
import { UsersService } from '../providers/users.service';
@Controller('api/v1/auth')
export class AuthController {
constructor(private readonly usersService: UsersService, private readonly jwtService: JwtService) {}
/**
* 230129 - Local 로그인을 위한 User 생성
*
* @tag auth
* @param CreateUserDto 유저를 생성하기 위해 필요한 최소한의 값 정의
*/
@TypedRoute.Post('sign-up')
async signUp(@TypedBody() createUserDto: CreateUserDto): Promise<ResponseForm<DecodedUserToken>> {
const { password, ...user } = await this.usersService.create(createUserDto);
return createResponseForm(user);
}
}
- POST api/v1/auth/sign-up
- body로는 CreateUserDto라는 타입으로 정의된 객체를 받아, 유저를 생성해주는 회원가입 메서드다.
CreateUserDto
import { UserEntity } from '../tables/user.entity';
export type CreateUserDto = Pick<
UserEntity,
'name' | 'nickname' | 'email' | 'password' | 'phoneNumber' | 'birth' | 'gender' | 'smsAdsConsent' | 'emailAdsConsent'
>;
- name
- nickname
- password
- phoneNumber
- birth
- gender
- smsAdsConsent
- emailAdsConsent
이러한 값들을 가지고 있다.
성별과 생일 등 민감한 정보를 제외하고는 무조건 입력해야만 회원가입할 수 있는 타입이다.
SDK 생성하기
import type SDK from '@nestia/sdk';
export const NESTIA_CONFIG: SDK.INestiaConfig = {
/**
* List of files or directories containing the NestJS controller classes.
*/
input: ['src/controllers', 'src/auth'],
/**
* Output directory that SDK would be placed in.
*
* If not configured, you can't build the SDK library.
*/
output: 'src/api',
/**
* Whether to assert parameter types or not.
*
* If you configure this property to be `true`, all of the function parameters would be
* checked through the [typia](<https://github.com/samchon/typia#runtime-type-checkers>).
* This option would make your SDK library slower, but would enahcne the type safety even
* in the runtime level.
*
* @default false
*/
// assert: true,
/**
* Whether to optimize JSON string conversion 2x faster or not.
*
* If you configure this property to be `true`, the SDK library would utilize the
* [typia](<https://github.com/samchon/typia#fastest-json-string-converter>)
* and the JSON string conversion speed really be 2x faster.
*
* @default false
*/
// json: true,
/**
* Whether to wrap DTO by primitive type.
*
* If you don't configure this property as `false`, all of DTOs in the
* SDK library would be automatically wrapped by {@link Primitive} type.
*
* For refenrece, if a DTO type be capsuled by the {@link Primitive} type,
* all of methods in the DTO type would be automatically erased. Also, if
* the DTO has a `toJSON()` method, the DTO type would be automatically
* converted to return type of the `toJSON()` method.
*
* @default true
*/
// primitive: false,
/**
* Building `swagger.json` is also possible.
*
* If not specified, you can't build the `swagger.json`.
*/
swagger: {
/**
* Output path of the `swagger.json`.
*
* If you've configured only directory, the file name would be the `swagger.json`.
* Otherwise you've configured the full path with file name and extension, the
* `swagger.json` file would be renamed to it.
*/
output: 'dist/swagger.json',
security: {
bearer: {
type: 'apiKey',
in: 'header',
name: 'Authorization',
},
},
},
};
export default NESTIA_CONFIG;
이번에는 SDK에 대해 설명할 것이기 때문에 아래 스웨거 관련된 설정은 볼 필요가 없다.
봐야 할 부분은 config.include, config.output으로 충분하다.
나의 경우 nestia sdk가 적용될 API들이 src/controllers 뿐만 아니라 src/auth에도 있었다.
나는 auth를 따로 관리하기 때문에 auth 폴더도 include에 포함시켜야 해서 이렇게 정의해두었다.
또한 output의 경우에는 src/api로 했다.
미리 말하는데, SDK는 우리가 작성한 API를 호출하는 함수로 구성된, TypeScript 파일들이다.
즉, 다시 한 번 컴파일하여 JS로 만들어야 패키지 형태로 프론트 개발자에게 전달할 수 있다.
생성 명령어는 npx nestia sdk 이다.
패키지 만들기
// tsconfig.api.json
{
"compilerOptions": {
/* Visit <https://aka.ms/tsconfig.json> to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["DOM", "ES2015"] /* Specify library files to be included in the compilation. */,
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./packages/api/lib" /* Redirect output structure to the directory. */,
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
"downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */,
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"stripInternal": true,
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src/api"]
}
일단 서버 프로젝트 폴더 내부에 tsconfig.json 대신 사용할 tsconfig.api.json을 만든다.
이건 이제 nestia를 이용해 생성된 SDK ( ts file ) 을 다시 한 번 JS 파일로 바꿔주기 위해 사용된다.
사실 클라이언트 ( = 프론트 개발자 ) 에게 주기 위해서는 빌드 결과물만 있어도 충분하지만,
이 SDK가 있다면 서버 입장에서는 e2e 테스팅 용도로 사용하는 데 매우 효과적이다.
그래서 내 생각엔, Nestia에서는 SDK를 한 번 거쳐서 packages를 생성하게끔 만든 것으로 보인다.
sdk를 컴파일하는 명령어는 tsc -p tsconfig.api.json 이다.
// package.json
{
"script": {
"sdk": "npx nestia sdk && npx tsc -p tsconfig.api.json"
}
}
그래서 나는 이와 같이 스크립트 명령어를 하나 추가해두었다.
SDK 사용 방법
import * as AuthApis from '../../api/functional/api/v1/auth'; // src/api/functional/api/v1/auth
const host = '<http://127.0.0.1:3000>';
AuthApis.sign_up.signUp({ host }, {
name: 'kakasoo',
nickname: 'kakasoo',
email: 'test@example.com',
password: 'string',
phoneNumber: '01085257658',
gender: true,
smsAdsConsent: true,
emailAdsConsent: true,
}).then((el) => console.log(el)); // NOTE : createdUser
추후 배포할 package나 SDK는 경로 상 다음과 같은 규칙이 있다.
api/functional 내부부터는 api의 경로와 일치한다는 점이다.
이 함수 호출은 typia.random<T>() 을 이용해서 아래와 같이 더 간편한 형태로 바꿀 수 있다.
AuthApis.sign_up.signUp({ host }, typia.random<CreateUserDto>()).then((el) => console.log(el));
typia의 random 함수는 지정한 타입 파라미터 형태에 맞게 값을 생성해주는 함수다.
이게 어떻게 가능한지는 아직 잘 모르겠지만(!?) 테스트에 정말로 유용하다.
이제 faker.js 따위는(?) 쓰지 않아도 된다.
import * as AuthApis from '../../api/functional/api/v1/auth'; // src/api/functional/api/v1/auth
import * as Apis from '../../api/functional';
/**
* 아래 두 함수 호출은 동일하다.
*/
AuthApis.sign_up.signUp(); // 파라미터 생략
Apis.api.v1.auth.sign_up.signUp(); // 파라미터 생략
아래 Apis.api.v1.auth.sign_up은, 내가 만든 AuthController를 보면 바로 이해할 수 있다.
내 회원가입 메서드의 경로는 `POST api/v1/auth/sign-up' 이었고, 컨트롤러 함수 이름은 signUp이었다.
하이픈(-) 기호는 언더바로 대체되었을 뿐이고, 최종적인 함수 네이밍은 컨트롤러와 일치한다.
따라서 import를 할 때, 원하는 API 경로만 갖고와도 되고, 전체를 갖고 온 다음에 함수로 작성해도 된다.
Nestia SDK를 활용한 e2e 테스트
import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from '../../app.module';
import { INestApplication } from '@nestjs/common';
import * as AuthApis from '../../api/functional/api/v1/auth';
import { CreateUserDto } from '../../models/dtos/create-user.dto';
import typia from 'typia';
describe('E2E article test', () => {
const host = '<http://localhost:3000>';
let app: INestApplication;
let testingModule: TestingModule;
beforeAll(async () => {
testingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = testingModule.createNestApplication();
await (await app.init()).listen(3000);
});
afterAll(async () => {
await app.close();
});
describe('auth API test', () => {
it('should return an array of articles', async () => {
const signUpResponse = await AuthApis.sign_up.signUp({ host }, typia.random<CreateUserDto>());
const createdUser = signUpResponse.data;
expect(createdUser.id).toBeDefined();
});
});
});
Testing Module을 만들고, app을 직접 실행시킨 다음에 API를 호출해서 테스트해볼 수 있다.
모든 테스트가 끝나면 afterAll을 이용해서 서버를 닫아주면 된다.
'프로그래밍 > NestJS' 카테고리의 다른 글
모든 타입이 추론되는 API 만들기 ( feat. Nestia ) (10) | 2023.03.26 |
---|---|
테스트 코드로 커뮤니케이션하기 (0) | 2023.03.12 |
Nestia TypeError 확인하기 (0) | 2023.03.01 |
Nestia Comment Tags (0) | 2023.03.01 |
You provided an invalid object where a stream was expected. (0) | 2023.02.24 |