본문 바로가기
게임서버-스파르타코딩NodeJs_7기/CH6 최종 프로젝트

테스트코드 작성 - achievement관련

by GREEN나무 2025. 3. 7.
728x90

 

📌 1. NestJS 의존성 주입 실패 문제

🔍 질문

UserGuard에서 UserService 의존성을 해결하지 못해 테스트가 실패하는 이유는?

💡 해결 방법

테스트 모듈에 UserService의 목업(mock) 버전을 제공하거나 UserModule을 임포트하여 의존성을 해결할 수 있습니다.
테스트의 격리성과 속도를 위해 목업 사용을 추천합니다.

목업 예시

providers: [
  {
    provide: UserService,
    useValue: {
      findUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Test User' }),
    },
  },
]

🛠 2. TypeScript 타입 불일치 문제 (sub-achievement.repository.ts)

🔍 질문

UpdateSubAchievementDto와 _QueryDeepPartialEntity 간의 타입 불일치 해결법은?

💡 해결 방법

  1. DTO 수정:
    UpdateSubAchievementDto에서 null을 허용하지 않도록 수정합니다.
    export class UpdateSubAchievementDto {
      @IsOptional()
      @IsDate()
      expiration_at?: Date;  // null 제거
    }
    
  2. null → undefined 변환:
    update 메서드 호출 전에 expiration_at의 값이 null인 경우 undefined로 변환하여 TypeORM과 호환되도록 합니다.
    async update(id: number, updateSubAchievementDto: UpdateSubAchievementDto) {
      const updateData = { ...updateSubAchievementDto };
      if (updateData.expiration_at === null) {
        updateData.expiration_at = undefined;
      }
      await this.entity.update(id, updateData);
    }
    

🎭 3. 테스트에서 서비스 목업(Mock) 사용 방법

🔍 질문

AchievementPService와 AchievementPController를 테스트할 때 외부 의존성을 어떻게 목업할 수 있을까요?

💡 해결 방법

  • jest.fn()을 사용하여 AchievementPRepositoryValkeyService의 메서드를 목업합니다.
  • 예를 들어, 아래와 같이 외부 의존성을 모두 목업하여 테스트의 격리성과 일관성을 유지할 수 있습니다.
repository.findSub.mockResolvedValue(mockSubAchievement);
repository.createP.mockReturnValue(mockAchievementP);

🔄 4. 목업 메서드에서 undefined 반환 문제

🔍 질문

repository.gem과 repository.dia 메서드를 목업할 때 undefined 반환 시 발생하는 오류는 어떻게 해결할 수 있을까요?

💡 해결 방법

  • TypeORM의 UpdateResult 객체를 모방하여 반환값을 설정합니다.
repository.gem.mockResolvedValue({ affected: 1 });
repository.dia.mockResolvedValue({ affected: 1 });

또는 명시적으로 UpdateResult 타입을 지정할 수도 있습니다.

import { UpdateResult } from 'typeorm';
repository.gem.mockResolvedValue({ affected: 1 } as UpdateResult);

📝 5. AchievementPService와 AchievementPController 테스트 케이스 작성법

🔍 질문

post, deleteByUserNSub, deleteByPId 메서드에 대한 테스트 케이스는 어떻게 작성해야 할까요?

💡 해결 방법

  • 성공 케이스:
    • 업적 생성, 삭제, 보상 지급 등 정상 동작 여부를 검증합니다.
  • 예외 케이스:
    • 중복 업적 생성, 존재하지 않는 업적 삭제 등 오류 발생 상황을 검증합니다.
  • 컨트롤러 테스트:
    • 해당 컨트롤러에서 서비스 메서드가 올바르게 호출되는지 확인합니다.

❌ 6. 테스트 실패 원인 및 해결 방법

🔍 질문

AchievementService의 create 메서드 테스트가 실패하는 이유와 해결책은 무엇인가요?

💡 해결 방법

  • 반환된 객체에 예상치 못한 추가 필드(예: achievement_c, created_at 등)가 포함되어 있어 toEqual 검증이 실패합니다.
  • 해결 방안 1: 기대 객체(mockAchievement)에 누락된 필드를 추가합니다.
  • 해결 방안 2: expect(result).toEqual(mockAchievement) 대신 toMatchObject()를 사용하여 필요한 필드만 부분적으로 검증합니다.

예시 코드

describe('AchievementService', () => {
  let service: AchievementService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AchievementService,
        {
          provide: AchievementRepository,
          useValue: {
            create: jest.fn().mockResolvedValue({
              id: 1,
              title: 'Test Achievement',
              content: 'Test Content',
              category: 'JEJU_TOUR',
              expiration_at: '2025-12-31T00:00:00.000Z',
              achievement_images: ['mock-image-url'],
              reward: { gem: 100, dia: 5 },
              created_at: '2025-01-01T00:00:00.000Z',
              updated_at: '2025-01-01T00:00:00.000Z',
              deleted_at: null,
            }),
          },
        },
      ],
    }).compile();

    service = module.get<AchievementService>(AchievementService);
  });

  it('should create an achievement successfully', async () => {
    const mockAchievement = {
      id: 1,
      title: 'Test Achievement',
      content: 'Test Content',
      category: 'JEJU_TOUR',
      expiration_at: expect.any(String),
      achievement_images: ['mock-image-url'],
      reward: { gem: 100, dia: 5 },
      created_at: expect.any(String),
      updated_at: expect.any(String),
      deleted_at: null,
    };

    const result = await service.create({ /* 유효한 DTO */ });
    expect(result).toEqual(mockAchievement);
    // 또는 유연한 검증을 위해
    // expect(result).toMatchObject({ id: 1, title: 'Test Achievement' });
  });
});

🎯 결론

  • AchievementService: 반환 객체와 기대 객체의 필드를 일치시키거나, toMatchObject()를 사용해 부분 검증을 적용
  • 컨트롤러 테스트: UserGuard 의존성(예: UserService, JwtService, ConfigService, ValkeyService 등)을 목업하여 추가
  • SubAchievementRepository: expiration_at의 타입 불일치를 해결하기 위해 null을 undefined로 변환하거나 DTO를 수정
  • 서비스 목업: 외부 의존성(예: AchievementPRepository, ValkeyService 등)을 올바르게 목업하고, 반환값 문제를 해결

📌 테스트를 실행할 때 명령어.

# 전체 테스트 파일 실행
npm test 

# 특정 테스트 파일 실행. 하나만 고르세요
npx jest path/to/your-test-file.test.js
npm test -- path/to/your-test-file.test.js