TaskShareApp(バックエンド) 苦戦歴 サインアップ
こんにちは!むらぴょんです!
今回は自分のポートフォリオを作る中で苦戦した箇所をピックアップしていきたいと思います。
マイグレーション時のビルド忘れ
これは私は結構やりがちなミスであり、詰まったポイントです。
マイグレーションファイル作成後は、必ず
npm run build
を忘れずに実行し、上記コマンドを実行後にマイグレーションを実行するようにしましょう。
パスワード一致確認のデコレータが難しすぎた
どうやってバリデーション退場オブジェクトから比較する値を取得すればいいのか、失敗時のメッセージ返却はどうするのか等、考えながらやってみましたがとにかく難しく感じました。(とにかく調べまくってなんとか実装させました・・・。)
AppModuleとAuthModuleの更新忘れ
これはサインアップに限らず、今後のログイン等の実装にも大きくかかわってくるため、ここはなんとか直していきたいところ・・・
@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 {}
@Module({
// 👇 importとTypeOrmModuleを追加
imports: [
TypeOrmModule.forFeature([User]),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
テストケースの作成が難しい(サービス編)
特にテスト作成時のテスト用モジュール作成やリポジトリの依存注入等が全然わからなかった。
// 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パターンとは
-
Arrange(準備)
- ダミーデータ(
dto
)を用意 - モックの戻り値や挙動を設定(
mockReturnValue
/mockResolvedValue
) - 依存関係を仕込む
- ダミーデータ(
-
Act(実行)
- テスト対象のメソッドを呼び出す(
const result = await service.signUp(dto);
)
- テスト対象のメソッドを呼び出す(
-
Assert(検証)
- 呼び出し回数(
toHaveBeenCalledTimes
) - 呼び出し引数(
toHaveBeenCalledWith
) - 戻り値の中身(
toEqual
/toMatchObject
/toHaveProperty
など)
- 呼び出し回数(
上記を1.から順番に記述する必要があった。
❌ ダメな例
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
✅ よい例
(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の不変性のテストは値が正しいかではなく、変化していないかを確認する
expect(dto).toMatchObject(
name: 'dummy',
email: 'test@dummy.com',
password: 'dummy123',
confirmPassword: 'dummy123',
imageId: '5d78f017-ef80-fbbf-3aad-f3f5d6c10043',
introduction: 'Hello, Elden',
});
これだと、値が正しいかのチェックになってしまう。
✅ 正:以下のようにSignUpメソッド実行前と後で変化していないかを確認する方法が望ましい。
// Arrange
const before = structuredClone(dto);
//Act
await controller.signUp(dto);
// Assert
expect(dto).toEqual(before);
サインアップ編の記事が完成しました!
序盤からかなり苦戦しましたが、サインアップ編の記事が完成しました!
よければご覧ください!
Discussion