콘솔 내용

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.527 s, estimated 2 s

Ran all test suites.

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."

 

테스트를 한 후에 뭔가를 멈추지 않았다고 한다. 

 

콘솔에서 알려준대로 package.json 에서 "test:e2e" 에  --detectOpenHandles 추가

그 결과 해당 문구는 뜨지 않았다.

하지만 테스트가 끝나지 않아 내가 강제로 종료를 해야하는 상황이 발생.

 

이유는 Jest 공식문서: -- detectOpenHandles 를 참고해보니

이 옵션을 사용하면 성능이 저하되며 디버깅을 할때만 사용하라고 한다.

 

옵션을 추가하지 않고 해결하기 위해 다시 검색을  했다.

How To Fix: Jest Did Not Exit 에 따르면

나의 경우에는 app을 이용하여 연결을 하였지만 app 것을 끝내지 않아서 발생한 것 같다.

 

아래의 코드를 마지막에 추가하고 실행해보니 테스트가 끝나후 안전하게 종료 되었다.

afterAll(async () => {
    await app.close();
  });

 

 

e2e 테스트 중에 연결된 것 ( app, database) 은 테스트가 끝나면 모두 종료하자.

e2e 테스트를 하다가 발생한 에러

ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)... Error: DataSource with name "default" has already added.

 

검색을 하여 찾은 아래의 링크를 따라 해결

https://github.com/Aliheym/typeorm-transactional/issues/10

 

Error when running E2E tests with Nest.js · Issue #10 · Aliheym/typeorm-transactional

Hi there, thanks for the package! It really is super useful. My issue is that we can't run our E2E tests with it as of now. We get the console error Error: DataSource with name "default" has alread...

github.com

 

두가지 해결 방식

방식 1. getDataSourceByName('default') 추가

import { addTransactionalDataSource, getDataSourceByName } from 'typeorm-transactional';

// ...

TypeOrmModule.forRootAsync({
    dataSourceFactory: async (options) => {
      return getDataSourceByName('default') || addTransactionalDataSource(new DataSource(options));
    },
  });

 

방식 2. deleteDataSourceByName('default') 추가

import { addTransactionalDataSource, deleteDataSourceByName } from 'typeorm-transactional';

// ...

TypeOrmModule.forRootAsync({
    dataSourceFactory: async (options) => {
      deleteDataSourceByName('default');

      return addTransactionalDataSource(new DataSource(options));
    },
  });

 

각 방식에서 사용된 코드의 의미???

import { DataSource, EntityManager, Repository } from 'typeorm';
...
export type DataSourceName = string | 'default';
const dataSources = new Map<DataSourceName, DataSource>();

export const getDataSourceByName = (name: DataSourceName) => dataSources.get(name);
export const deleteDataSourceByName = (name: DataSourceName) => dataSources.delete(name);

 

 

방식 1 을 선택하여   getDataSourceByName('default') 코드를 추가하여 해결

//변경 전
 async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }
    return addTransactionalDataSource(new DataSource(options));
  },
  
// 변경 후
 async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }
    return getDataSourceByName('default') || addTransactionalDataSource(new DataSource(options));
  },

 

 

이렇게 하니 귀찮은 일이 발생했다.

1. 앞의 테스트에서 내가 생성한 유저를 다시 생성하려면 이미 있는 e-mail 이라는 문구가 발생

2. 앞의 테스트에서 내가 지운 유저를 또 지우려면 내가 원하는 204가 아니라 500 반환

 

beforeAll 을 이용하여서 하니 

  • 옵션< getDataSourceByName('default') >을 추가하지 않아도 해당 에러가 뜨지 않았다. Error: DataSource with name "default" has already added.
  • 테스트할때마다 내가 적은 expect 부분을 수정하지 않아도 되었다.

의문점

  • beforeEach 에서는 중복이 발생했는데 어떻게 beforeAll 에서는 중복이 발생하지 않을까

-> 데이터베이스를 확인해보니 역시나 중복이 발생. 중복을 방지하기 위해  

 

최종적으로 테이블을 드랍하고 동기화 하는 코드를 추가하여 마무리하였다.

 beforeAll(async () => {
    initializeTransactionalContext();
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    
    dataSource = moduleFixture.get<DataSource>(DataSource);
    
    // 데이터베이스 테이블 초기화 및 동기화
    await dataSource.dropDatabase();
    await dataSource.synchronize();
    
    // 토큰 발행
    token = generateTestToken();
    
    app = moduleFixture.createNestApplication();
    await app.init();
  });

 

※ 데이테베이스 테이블 초기화를 beforeEach에 사용하면 생성한 유저가 사라져서 jwt 토큰을 발행하는데 문제가 발생한다.

DI (Dependency Injection) : 의존성 주입 

  • 객체 지향 프로그래밍에서 중요한 디자인 패턴 중 하나
  • 객체가 필요로 하는 의존 객체를 외부에서 주입하하여 객체 간의 결합도를 낮추는 방식

 

사용하는 이유

의존성 주입을 통해 객체는 자신이 사용할 의존 객체를 직접 생성하지 않고, 외부에서 생성된 객체를 주입받기 때문입니다.

이로 인해 객체는 자신의 구현에만 집중할 수 있으며, 변경에 유연하게 대응할 수 있습니다.

 

장점

코드의 재사용성 증가, 코드의 유지보수성 향상, 객체 간의 결합도 감소, 단위 테스트 용이성 증가

 

 

 

Nest.js 에서 DI 하는 방법

  • DI는 의존성을 “런타임”시점에 주입시켜주는 기술
  • TypeScript는 컴파일 시점에만 인터페이스가 존재하고 “런타임” 시점에는 인터페이스가 사라지게 된다

→ DI컨테이너가 런타임 시점에 의존성을 주입시켜주지 못 해서 에러발생

 

프로바이더를 모듈에 등록할때 사용자 지정 프로바이더 문자열 토큰 주입 방식으로 해결

 

export interface TestRepository {
   outputText(text: string): void;
}
@Injectable()
export class TestConsoleLog implements TestRpository {
  outputText(text: string): void {
    console.log(text);
  }
}
@Module({
  imports: [TypeOrmModule.forFeature([Test])],
  controllers: [WorkoutLogController],
  providers: [TestService, 
         // provide에 문자열 토큰 지정
         { provide: 'test', useClass: TestConsoleLog }
         ],
})

    

@Injectable()
export class TestService {
  constructor(
    @Inject('test') //// Inject 데코레이터 사용해서 porivde에 등록한 문자열 토큰 사용
    readonly testRepository: TestRepository,
  ) {}
  
  outputMessage(message: string): void{
    this.testRepository.printText(message);
  }
  
}

 

문자열 토큰방식으로 프로바이더를 등록 후 의존하고 있는 인터페이스에 @Inject('등록한 문자열 토큰') 데코레이터를 달아주면 useClass에 명시한 구현체가 주입된다.

 

 

참고 자료

f-lab -스프링 프레임워크와 의존성 주입(DI) 이해하기

median - NestJS 인터페이스로 DI하기

오류 내용

[remote rejected] main -> main (refusing to allow a Personal Access Token to create or update workflow `.github/workflows/deploy.yml` without `workflow` scope)

 

Access Token 이  workflow 를 수정하거나 업데이트할 권한이 없다.

 

해결방법

깃허브 홈페이지에서 

Settings > Developer Settings > Token  으로 이동

 

발급된 토근에서  workflow 클릭 하여 허용된 목록을 수정한다.

Index signature 란

객체의 속성(property)에 동적인 키를 사용하여 자유롭게 구현할 수 있도록 도와주는 TypeScript의  문법이다.

 

구문 (Syntax)

{ [key: KeyType]: ValueType }

- [ ] 안에 key 의 타입을 설정

- key : 객체의 속성을 나타내는 이름

- KeyType : key 의 타입,  주로 string, number 을 사용

- ValueType : 각 키에 해당하는 값의 타입

 

장점

  1. 유연성: 사전에 정의되지 않은 키를 다룰 수 있다.
  2. 타입 안전성: 객체의 키와 값에 대한 타입을 지정하여 타입 안전성을 유지한다.
  3. 동적 데이터 처리: 동적인 데이터 구조를 처리하는 데 유용합니다. 예를 들어, 외부 API로부터 동적으로 키가 결정되는 데이터를 받을 때 유용하다.

단점

  1. 데이터 타입의 유연성 부족 : 객체 내에 다양한 타입의 값을 혼합해서 사용할 수 없다
  2. 유지보수성 감소 : 동적인 속성 이름을 사용하기 때문에, 객체의 구조를 한눈에 파악하기 어렵고, 추후에 구조를 변경하거나 디버깅할 때 어려움이 있을 수 있다.

예제

  • string 타입의 키
interface StringIndexSignature {
  [key: string]: number;
}

const example: StringIndexSignature = {};
example['one'] = 1;
example['two'] = 2;

console.log(example); // { one: 1, two: 2 }
  • number 타입의 키
interface NumberIndexSignature {
  [key: number]: string;
}

const example: NumberIndexSignature = {};
example[1] = 'one';
example[2] = 'two';

console.log(example); // { 1: 'one', 2: 'two' }
  • DTO 에 string 타입의 키 적용
interface AnimalDataDTO {
  [animal: string]: number;
}

export class ExampleDTO {
  animal: YearlyDataDTO;

  constructor() {
    this.animal = {};
  }
}

const example: ExampleDTO = new ExampleDTO();
example['tiger'] = 1
console.log(example) // { tiger: 1 }

+ Recent posts