🔥

TaskShareApp(バックエンド) 苦戦歴 サインアップ

に公開

こんにちは!むらぴょんです!

今回は自分のポートフォリオを作る中で苦戦した箇所をピックアップしていきたいと思います。

マイグレーション時のビルド忘れ

これは私は結構やりがちなミスであり、詰まったポイントです。
マイグレーションファイル作成後は、必ず

npm run build

を忘れずに実行し、上記コマンドを実行後にマイグレーションを実行するようにしましょう。

パスワード一致確認のデコレータが難しすぎた

どうやってバリデーション退場オブジェクトから比較する値を取得すればいいのか、失敗時のメッセージ返却はどうするのか等、考えながらやってみましたがとにかく難しく感じました。(とにかく調べまくってなんとか実装させました・・・。)

AppModuleとAuthModuleの更新忘れ

これはサインアップに限らず、今後のログイン等の実装にも大きくかかわってくるため、ここはなんとか直していきたいところ・・・

app.module.ts
@Module({
  // AuthModuleを追加
  imports: [
    UsersModule,
    AuthModule,  // ←ここ!
    ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('database.host'),
        port: configService.get('database.port'),
        username: configService.get('database.username'),
        password: configService.get('database.password'),
        database: configService.get('database.name'),
        entities: ['dist/**/entities/**/*.entity.js'],
        synchronize: false,
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
auth.module.ts
@Module({
  // 👇 importとTypeOrmModuleを追加
  imports: [
    TypeOrmModule.forFeature([User]),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

テストケースの作成が難しい(サービス編)

特にテスト作成時のテスト用モジュール作成やリポジトリの依存注入等が全然わからなかった。

auth.service.spec.ts
// userRepoの定義の仕方が分からなかった。
userRepo = {
  create: jest.fn(),
  save: jest.fn(),
} as any;

const module: TestingModule = await Test.createTestingModule({
  controllers: [AuthController],
  providers: [
    AuthService,
    // provideの型等もわからなかった
    // { provide: getRepositoryToken(User), useClass: Repository }とやりがち・・・
    { provide: getRepositoryToken(User), useValue: userRepo },
  ],
}).compile();

spyで確認するってなんぞや??

spyで確認しようもそのやり方やspyメソッドってそもそもなんぞや!というところがありました。

expectの引数とtoEqual等の引数の型が全然合わない

これは私が注意深く確認していればそこまで苦戦していなかったのではと思っています。
例として以下のような感じです。

const spyHash = jest.spyOn(bcrypt, 'hash').mockResolvedValue('hashed');
const createdUser = {
    id: 1,
    name: dto.name,
    email: dto.email,
    imageId: dto.imageId,
    introduction: dto.introduction,
    hashedPassword: 'hashed',
    imageUrl: null,
    createdAt: now,
    updatedAt: now,
};

// 関数オブジェクトと文字列を比較してるので必ず失敗する。
expect(spyHash).toEqual(createdUser.hashedPassword)

AAAパターンの順番がわからなかった

AAAパターンとは

  1. Arrange(準備)

    • ダミーデータ(dto)を用意
    • モックの戻り値や挙動を設定(mockReturnValue / mockResolvedValue
    • 依存関係を仕込む
  2. Act(実行)

    • テスト対象のメソッドを呼び出す(const result = await service.signUp(dto);
  3. Assert(検証)

    • 呼び出し回数(toHaveBeenCalledTimes
    • 呼び出し引数(toHaveBeenCalledWith
    • 戻り値の中身(toEqual / toMatchObject / toHaveProperty など)

上記を1.から順番に記述する必要があった。

❌ ダメな例

auth.service.spec.ts
expect(jestHash).toHaveBeenCalledTimes(1);    // Assert
expect(result.hashedPassword).toBe('hashed');    // Assert
(userRepo.create as jest.Mock).mockReturnValue(createdUser);    // Arrange
expect(userRepo.create).toHaveBeenCalled();    // Assert
const result = await service.signUp(dto);    // Act

✅ よい例

auth.service.spec.ts
(userRepo.create as jest.Mock).mockReturnValue(createdUser);    // Arrange
const result = await service.signUp(dto);    // Act
expect(jestHash).toHaveBeenCalledTimes(1);    // Assert
expect(result.hashedPassword).toBe('hashed');    // Assert
expect(userRepo.create).toHaveBeenCalled();    // Assert

テストケースの作成が難しい(コントローラ編)

SUTを二重に呼んでしまいがち

コントローラのテスト時、expect(controller.signUp(dto)).toHaveBeenCalledWith(...) のように、toHaveBeenCalledWith はモック関数用の matcherのため controller.signUp はモックではなく、ここでもう一度実行されるから、テストが壊れる/回数がズレる。

✅ 正:モックした service.signUp に対して toHaveBeenCalledWith(dto) / toHaveBeenCalledTimes(1) を検証する。

spyの戻り値と実際の戻り値の取り違えが多かった

const spy = jest.spyOn(service, 'signUp').mockResolvedValueOnce(...) とした後に expect(result).toEqual(spy) はNG。spy は関数(モック)であって返り値ではないということ。

✅ 正:mockResolvedValueOnce には実値を渡し、result をその実値と比較する。

expectの引数にモックではないものを入れてしまう

expect(controller.signUp(dto)).toHaveBeenCalledTimes(1); というような書き方が多くなってしまっていた。
controller.signUp はモックではないため、toHaveBeenCalledTimes はエラーになる。

✅ 正:expect(service.signUp).toHaveBeenCalledTimes(1); というようにexpectの引数にはモックを入れる。

DTOの不変性のテストは値が正しいかではなく、変化していないかを確認する

auth.controller.spec.ts
expect(dto).toMatchObject(
  name: 'dummy',
  email: 'test@dummy.com',
  password: 'dummy123',
  confirmPassword: 'dummy123',
  imageId: '5d78f017-ef80-fbbf-3aad-f3f5d6c10043',
  introduction: 'Hello, Elden',
});

これだと、値が正しいかのチェックになってしまう。

✅ 正:以下のようにSignUpメソッド実行前と後で変化していないかを確認する方法が望ましい。

auth.controller.spec.ts
// Arrange
const before = structuredClone(dto);

//Act
await controller.signUp(dto);

// Assert
expect(dto).toEqual(before);

サインアップ編の記事が完成しました!

序盤からかなり苦戦しましたが、サインアップ編の記事が完成しました!
よければご覧ください!

https://qiita.com/Murapyon/private/7d1ada1aceb53d334266

Discussion