Jestを使ってexpressをTDDをしたい GraphQL編

[前回](https://qiita.com/shun_xx/items/54e9f478ba7a8a508c10)は単純なルーティングをTDDできるようにしました。
今回は、QraphQL でもTDDできるようにしたので、その備忘録です。

今回の環境は[こちら](https://github.com/shun/tdd-express-typescript/tree/0.2-tdd-for-graphql) で試せます。

# 変更点

- TypeORM のDB操作処理の追加
- GraphQL（Apollo） を追加
- Resolverの追加

# TypeORMのDB操作処理の追加

TypeORMを使用してDBアクセスするための設定ファイルを準備します。

## DB接続情報の準備
typeorm のcliを使用してプロジェクトを作成すると、package.json と同じディレクトリに
ormconfig.json というファイルが生成されます。

単に使うだけなら、このままでも良いのですが、jestのテストでも使用するのにはちょっと使いづらいので
ormconfig.jsとして保存し、テストケース内でimportできるように設定情報もexportしておきます。

自分でちょっとハマったのですが、「"name":"default"」を追記してあげないと
自動で読み込んでくれないので追記しておきます。

```diff:ormconfig.js
-{
+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 で呼んで上げるようにします。

```typescript:server.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);

    }
  }
}

```

```typescript:index.ts
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しておきます。

```typescript:UserResolver.spec.ts
let conn;
beforeAll(async () => {
  conn = await createConnection(
    DBConfig
  );
});

afterEach(async () => {
});

afterAll(async() => {
  conn.close();
});
```

今回は1つのテストケースだけですが、各テストケースで同様のデータを
登録してテストするのであれば、以下のように定義しておくと便利かと思います。
afterEachで、Userテーブルを空にしています。

```typescript:UserResolver.spec.ts
  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内で保存したユーザ情報と同じ

```typescript:UserResolver.spec.ts
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を定義します。

```typescript:UserResolver.ts
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を使用するので、以下のように変更しています。

```diff:User.ts
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 になりました。

```bash
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できる準備ができました。
長くなりましたが、ここまでお付き合いしていただきありがとうございます。