abol-server-testing과 SQLite로 종합 테스트를 해보도록 하겠습니다.
이른바 apol-server-testing
Apollo에서 제공하는 테스트용 도구는 서버의 전체적인 통합 테스트를 더욱 간단하게 하는 도구를 제공합니다.
const { query, mutate } = createTestClient(server);
mutate({
mutation: UPDATE_USER,
variables: { id: 1, email: "[email protected]" },
});
createTestClient
는 HTTP 서버를 만들지 않고 내부의resolver를 실행할 수 있는 방법이다.이를 기점으로 Resolver에서 보는 종합 테스트를 진행할 수 있다.샘플 항목
여기 창고에 Push 샘플이 있으니 clone에서 시험해 보세요.
git clone https://github.com/suzukalight/sample-apollo-server-testing-integration-test
cd sample-apollo-server-testing-integration-test
yarn
다음과 같은 기술을 사용한다.Apollo Server w/Express + apollo-server-testing
TypeORM w/SQLite
시험의 필요조건과 대상
deleteUser mutation 고려
mutation DeleteUser($input: DeleteUserRequest) {
deleteUser(input: $input) {
user {
id
email
}
}
}
사용자를 삭제하는 mutation deleteUser를 만들고 종합적인 테스트를 고려합니다.테스트 요구 사항
등, 이번에는 통합 테스트에서 확인할 예정이다.
실제로 이러한 검사를 요구하는 대부분은usecase의 단원 테스트를 통해 실현할 수 있다.종합테스트에서'부품의 결합이 정확한지'를 확인해야 하기 때문에 종합테스트에서degray와 인터페이스가 일치하지 않는지 확인하는 등 단원테스트를 두껍게 하면 피라미드에서도 더욱 좋다
통일 테스트의 실현
구체적인 실복에 들어가다.설치의 큰 절차로서
매크로 패키지 설치
yarn add -D apollo-server-testing
각 테스트마다 별도의 DB 생성
test/integration/setup/database.ts
import { Connection, createConnection } from "typeorm";
import { v4 as uuidv4 } from "uuid";
import fs from "fs";
// ランダムなDB名を生成
export const getRandomDbPath = () => `./test_db/${uuidv4()}.sqlite`;
// 各テストごとに独立したDBを作成し、テストの独立性を担保する
export const createDbConnection = async (randomDbPath: string) =>
createConnection({
type: "sqlite",
name: randomDbPath,
database: randomDbPath,
entities: [User],
synchronize: true,
});
// DBファイルを削除
export const deleteDbFile = (dbPath: string) => {
fs.unlinkSync(dbPath);
};
실제 환경의 DB를 테스트에 활용할 수 없어 SQLite로 DB를 이동하기로 했다.TypeORM에서는 설정type: 'sqlite', database: filepath, synchronize: true
을 통해 DB에 연결할 때 자동으로 DB 파일을 생성하고 모드 동기화를 진행한다.이 경우 각 테스트가 describe 단위로 병행되므로 DB 공유가 다른 테스트 결과를 오염시킬 수 있습니다.그래서 우리는 하나의 데이터베이스 구조를 테스트하는 것을 제정했다.
SQLite의 경우 파일 기반 또는 스토리지 기반 기반 DB를 빠르게 만들 수 있기 때문에 통합 테스트처럼 반복적으로 수행하는 데 유리하다.메모리 기반이 없는 이유는 CI에서 실행할 때 용기의 메모리를 깨물어 의외의 실패가 발생하지 않도록 하기 때문이다.
테스트 서버 시작
test/integration/setup/apollo-server.ts
import {
createTestClient,
ApolloServerTestClient,
} from "apollo-server-testing";
// ...
export const createApolloServerForTesting = (
dbConnection: Connection,
actor?: UserDto
): ApolloServerTestClient => {
// graphql-codegen でバンドルしたスキーマファイルを使用
const schema = loadSchemaSync(
path.join(__dirname, "../../../src/schema/schema.graphql"),
{
loaders: [new GraphQLFileLoader()],
}
);
// resolverをスキーマと連結
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers,
});
// ApolloServerでGraphQLサーバを起動
const server = new ApolloServer({
schema: schemaWithResolvers,
context: () => getContext(dbConnection, actor),
});
// テスト用のGraphQLクライアントを生成
const testClient = createTestClient(server);
return testClient;
};
Apollo Server의 실례를 만들고 apolo-server-testingcreateTestClient
에 제출하면 테스트용 클라이언트를 얻을 수 있습니다.이 고객에서query와mutate를 호출하여 종합 테스트를 진행합니다.또한 Apollo Server의 인스턴스는 Express를 설정할 필요가 없습니다.이 종합 테스트는 HTTP 클라이언트를 사용하지 않고 테스트 클라이언트에서 Resolver를 직접 호출할 수 있습니다.
각 테스트에 대한 서버 및 DB 생성
describe("deleteUser", () => {
describe("Admin", () => {
const actor = {
id: "1",
email: "[email protected]",
roles: [RoleTypes.Admin],
}; // Adminロールのactor
const randomDbPath = getRandomDbPath(); // テストごとに固有のファイルを作成
let dbConnection: Connection;
let testClient: ApolloServerTestClient | undefined;
beforeAll(async () => {
dbConnection = await createDbConnection(randomDbPath); // DBの作成とマイグレーション
await seedAll(dbConnection); // UserをDBに流し込む
testClient = createApolloServerForTesting(dbConnection, actor); // Adminをactorとしてサーバを起動
});
afterAll(async () => {
await dbConnection.close();
deleteDbFile(randomDbPath); // DBファイルを削除し、テストごとにDBを破棄
});
// ...
});
});
deleteUser 테스트에서 actor 전환을 위한 2가지 테스트를 준비했다.actor가 Admin 역할이면 모든 사용자를 삭제할 수 있고, Member 역할이면 자신만 삭제할 수 있습니다.각 테스트 그룹이 초기화될 때 테스트 클라이언트와 DB를 생성하고 끝날 때 폐기합니다.모든 actor에 DB를 만들면 테스트 방안의 영향 범위를 국부적으로 설정하여 병행 실행에 편리하게 할 수 있습니다.
{query,mutate} 병합 테스트 사용하기
test("OK: Adminロールで、エンティティの削除ができた", async () => {
const result = await testClient?.mutate({
mutation: DELETE_USER,
variables: {
input: {
id: "3",
},
},
});
const { user } = result?.data?.deleteUser ?? {};
expect(user?.email).toBe("[email protected]"); // 削除したユーザの情報が返ってくる
});
test("NG: 存在しないIDを指定した", async () => {
const result = await testClient?.mutate({
mutation: DELETE_USER,
variables: {
input: {
id: "99999",
},
},
});
const { data, errors } = result ?? {};
expect(data?.deleteUser).toBeNull(); // dataはnullが返ってくる
expect(errors?.length).toBeGreaterThan(0); // errorsにエラー内容が含まれている
});
각 테스트에서 mutate
방법으로 deleteUser의 Resolver를 실행하고 assert 형식으로 기대에 부합되는지 회답합니다.실행
$ yarn test
yarn run v1.22.4
$ env-cmd -f .env.default jest
PASS src/entity/user/__tests__/index.ts
PASS src/policy/decision/__tests__/common.ts
PASS src/entity/common/Password/__tests__/encrypt.ts
PASS src/entity/common/Email/__tests__/index.ts
PASS src/entity/common/Password/__tests__/entity.ts
PASS test/integration/User/__tests__/deleteUser.ts
Test Suites: 6 passed, 6 total
Tests: 59 passed, 59 total
Snapshots: 0 total
Time: 4.087 s
Ran all test suites.
✨ Done in 4.85s.
PASS가 정상입니다.수중 환경에서 2종 5종의 종합테스트를 수행하는 데 걸리는 시간은 4~7초다.Google TestSize에서 Meduim 사이즈 테스트에 걸리는 시간은 최대 300초로 매우 짧은 시간이라고 할 수 있습니다.
끝말
apolo-server-testing과 sqlite를 통해 Resolver의 종합 테스트를 신속하게 진행할 수 있으며 기존 환경에 영향을 주지 않습니다.단원 테스트와 E2E 테스트 사이의 중요한 테스트를 쉽게 구축할 수 있는 메커니즘이 있기 어렵다.
이번 종합 테스트는 Admin 스크롤 막대만 있는 경우 사용자를 삭제할 수 없다는 문제를 없앴다.종합테스트의 결과인가... 조금 미묘하지만 쓰기 테스트를 통해 BUG 상태에서 발표하지 않아도 된다.
전제적으로 피라미드 아래의 단원 테스트를 두껍게 하고 고속으로 수행할 수 있는 종합 테스트가 더 많으면 E2E 테스트와 수동 테스트에 의존하지 않아도 품질 유지 능력을 높여야 한다.합병 테스트도 계속 개량하고 싶다.이렇게 하면degray가 두렵지 않아요!대략!
보너스
아래 창고에는 종합테스트를 진행하는 항목이 더 공개됐으니 함께 보시기 바랍니다.
Reference
이 문제에 관하여(abol-server-testing과 SQLite로 종합 테스트를 해보도록 하겠습니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/suzukalight/articles/apollo-server-testing-sqlite텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)