DIしたいインターフェースの内容
テストを行いたい呼び出し可能関数が、次のインターフェースを実装したクラスに依存しているとします。
import type { User } from "~/domain/model/User";
export interface UserService {
updateEmail: (uid: string, email: string) => Promise<User | undefined>;
updateName: (uid: string, name: string) => Promise<User | undefined>;
}
この時に、updateEmail()
メソッドの返り値によって呼び出し可能関数の結果が変わるとします。
import { container } from 'tsyringe';
...
export const updateEmail: CallableMethod = async (
data,
context,
): Promise<UpdateEmailResponseData> => {
const userService = container.resolve<UserService>('UserService');
const result = await userService.updateEmail(uid, newEmail);
if (!result) {
throw new functions.https.HttpsError('internal', 'メールアドレスの更新を行うことができません');
}
return {
newEmail: newEmail,
};
}
偽の実装クラスの作成
この場合、UserService
インターフェースのクラスをテストごとに何度も実装するのを避けるため、偽の実装クラスを作成します。
import type { UserService } from "../UserService";
import { User } from "~/domain/model/User";
import type { User as UserModel } from '~/infrastructure/database/model/User';
const userModel: UserModel = {
...
};
export class FakeUserServiceImpl implements UserService {
async updateEmail(uid: string, email: string): Promise<User | undefined> {
const user: User = new User(userModel);
return user;
}
async updateName(uid: string, name: string): Promise<User | undefined> {
const user: User = new User(userModel);
return user
}
}
成功用のテストを実装
そして、成功用のテストを実装します。
import 'jest';
import 'reflect-metadata';
import firebaseFunctionsTest from 'firebase-functions-test';
import * as firebaseFunctions from 'firebase-functions';
const test = firebaseFunctionsTest();
import { updateEmail } from '~/routes/staymile/tab-four/change-email/updateEmail';
import { container } from 'tsyringe';
import { UserService } from '~/application/service/UserService';
import { FakeUserServiceImpl } from '~/application/service/impl/FakeUserServiceImpl';
describe('updateEmail', () => {
it('成功するテスト', async () => {
const uid = 'user-id';
const newEmail = 'newemail@example.com';
container.register<UserService>('UserService', {
useClass: FakeUserServiceImpl,
});
const wrapped = test.wrap(firebaseFunctions.https.onCall(updateEmail));
const result = await wrapped(
{
newEmail: newEmail,
},
{
auth: {
uid: uid,
},
},
);
expect(result.newEmail).toBe(newEmail);
});
}
次に失敗するテストを書きたいので、updateEmail()
メソッドがundefined
を返すようにしたいです。そのため、新たに偽のクラスを作成するという方法もあるのですが、あまりに冗長です。
偽のクラスの再実装を防ぐ
そこで、偽の実装クラスの再生成を防ぐためにjest.spyOn
を使って、偽クラスのメソッドの返り値を置き換えることにします。
jest.spyOn(FakeUserServiceImpl.prototype, 'updateEmail').mockResolvedValue(undefined);
失敗テストの作成
これによりupdateName()
メソッドがundefined
を返した場合に、例外が発生することをテストすることができるようになりました。
it('メールアドレスの変更に失敗した場合はエラーを発生させる', async () => {
jest.spyOn(FakeUserServiceImpl.prototype, 'updateEmail').mockResolvedValue(undefined);
const uid = 'user-id';
const newEmail = 'newemail@example.com';
container.register<UserService>('UserService', {
useClass: FakeUserServiceImpl,
});
const wrapped = test.wrap(firebaseFunctions.https.onCall(updateEmail));
expect(
wrapped(
{
newEmail: newEmail,
},
{
auth: {
uid: uid,
},
},
),
).rejects.toThrowError();
});
参考にした記事