0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

前回は単純なルーティングを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"」を追記してあげないと
自動で読み込んでくれないので追記しておきます。

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 で呼んで上げるようにします。

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);

    }
  }
}

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しておきます。

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

afterEach(async () => {
});

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

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

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内で保存したユーザ情報と同じ
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を定義します。

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

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 になりました。

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?