前回は単純なルーティングをTDDできるようにしました。
今回は、QraphQL でもTDDできるようにしたので、その備忘録です。
今回の環境はこちら で試せます。
変更点
- TypeORM のDB操作処理の追加
- GraphQL(Apollo) を追加
- Resolverの追加
TypeORMのDB操作処理の追加
TypeORMを使用してDBアクセスするための設定ファイルを準備します。
DB接続情報の準備
typeorm のcliを使用してプロジェクトを作成すると、package.json と同じディレクトリに
ormconfig.json というファイルが生成されます。
単に使うだけなら、このままでも良いのですが、jestのテストでも使用するのにはちょっと使いづらいので
ormconfig.jsとして保存し、テストケース内でimportできるように設定情報もexportしておきます。
自分でちょっとハマったのですが、「"name":"default"」を追記してあげないと
自動で読み込んでくれないので追記しておきます。
-{
+module.exports = {
+ "name": "default",
"type": "mysql",
"host": "db",
"port": 3306,
"username": "test",
"password": "test",
"database": "testdb",
"synchronize": false,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}
DB接続処理の追加
DBの接続はTypeORMだと、createConnectionのメソッドを呼んどいてあげれば、
DBにアクセス出来ます。
そのため、AppServer のsetup時に呼んであげれば良いかと思っていたのですが、
このタイミングでcreateConnectionを呼んでしまうと、テスト実行時に
同じ名前の接続情報を生成するため、テスト側でDBにアクセスできません。
そのため、以下のようにindex.ts で呼んで上げるようにします。
export class AppServer {
public async setup(): Promise<void> {
try {
const schema = await buildSchema({
resolvers: [__dirname + "/resolver/**/*.ts"],
});
this.server = new ApolloServer({
schema,
playground: true,
});
this.server.applyMiddleware({ app: this.app });
this.setupRoutes();
// 本当はここで呼ぼうとしたけど、ここで呼んでしまうと
// テストケースでDBアクセス出来ない。
// テストケースでアクセスできるように新たに接続をしようとすると、
// 別名の接続を用意しなくてはいけないので手間がかかる
// await getConnection();
} catch (err) {
console.log(err);
}
}
}
import { AppServer } from "./server";
import { createConnection } from "typeorm";
async function bootstrap() {
const server = new AppServer();
await server.setup();
await createConnection(); // サーバとは別の箇所で接続を制御する
server.start();
}
bootstrap();
テストケースの追加
DBの接続も分離してテストできるようになったのでテストケースを追加します。
各テストケースでDBアクセスできるようにするために、
beforeAll でDBの接続を確立しておきます。
また、afterAllでお行儀良くDBの接続をcloseしておきます。
let conn;
beforeAll(async () => {
conn = await createConnection(
DBConfig
);
});
afterEach(async () => {
});
afterAll(async() => {
conn.close();
});
今回は1つのテストケースだけですが、各テストケースで同様のデータを
登録してテストするのであれば、以下のように定義しておくと便利かと思います。
afterEachで、Userテーブルを空にしています。
let server: AppServer;
let user: User;
beforeEach(async() => {
server = new AppServer();
server.setup();
user = await getRepository(User).create({
firstName: "たろう",
lastName: "ほげほげ",
age: 30
});
await getRepository(User).save(user);
});
afterEach(async() => {
await getRepository(User).clear();
});
今回のテストとしては以下をテストします。
- クエリを発行すると、ステータスコード200を受け取る
- id=1のユーザ情報を取得し、beforeEach内で保存したユーザ情報と同じ
import request from "supertest";
import { AppServer } from "../src/server";
import { User } from "../src/entity/User";
import { getRepository, getConnection, createConnection } from "typeorm";
import DBConfig from "../ormconfig";
type UserResponseBody = {
lastName: string,
firstName: string,
age: number
};
describe("UserResolver", () => {
describe("return 200", () => {
it("id:1 user", async () => {
const result = request(server.app)
.post("/graphql")
.send({
query: `{
user(id: 1) {
lastName
firstName
age
}
}`
})
.expect(200)
.then((res) => {
const actual: UserResponseBody = res.body.data.user;
expect(user.firstName).toBe(actual.firstName);
expect(user.lastName).toBe(actual.lastName);
expect(user.age).toBe(actual.age);
});
return result;
});
});
});
これで、テストの準備が完了です。
「yarn test」を実行すると、無事redになるので、引き続き
テストが通るように修正していきます。
Resolverの追加
自分の場合、type-graphqlというパッケージを使用すると、
スキーマ等をアノテーションで定義することができるので便利に使用しています。
今回は、指定idのユーザ情報を取得するだけのResolverを定義します。
import { Resolver, Mutation, Int, Arg, Query } from "type-graphql";
import { getRepository, getConnection, createConnection } from "typeorm";
import { User } from "../entity/User";
@Resolver()
export class UserResolver {
@Query(() => User)
async user(
@Arg("id") id: number
): Promise<User> {
const user = await getRepository(User).findOne({
where: {id: id}
})
return user;
}
}
TypeORM cliでプロジェクトを生成するとUser.tsというentityが自動で作成されます。
type-graphqlを使用するので、以下のように変更しています。
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
+import { ObjectType, Field } from "type-graphql";
@Entity()
+@ObjectType()
export class User {
@PrimaryGeneratedColumn()
+ @Field()
id: number;
@Column()
@Field()
+ firstName: string;
@Column()
@Field()
+ lastName: string;
@Column()
@Field()
+ age: number;
}
これで一通り揃ったはずなので、テストを実行します。
無事テストがgreen になりました。
node@94775e8238be:~/app$ yarn test
yarn run v1.22.4
warning package.json: No license field
$ jest
PASS test/server.spec.ts (5.542 s)
PASS test/UserResolver.spec.ts (5.826 s)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.635 s
Ran all test suites.
Done in 7.41s.
とりあえず、これでQraphQLを使用したexpressの開発をTDDできる準備ができました。
長くなりましたが、ここまでお付き合いしていただきありがとうございます。