2
0

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 1 year has passed since last update.

Webアプリ構築カレンダーAdvent Calendar 2023

Day 16

【Day 16】ブログ一覧を取得し、表示する - BFF Module

Last updated at Posted at 2023-12-15

はじめに

スライド17.PNG


2023年アドベントカレンダー16日目です。

現在「ユーザーはトップページでブログの一覧を見ることができる」を進めています。

image.png

前回はGraphQLModuleの設定を行いました。
今回はメインの実装を進めていきます。

E2Eテスト

nest newでNestJSを作成したときにすでにE2Eテストの概形はできているので、テストを追加していきます。

もっとGraphQLサーバーに特化した書き方があるかもしれません

POSTでqueryを受け取り、期待するレスポンスを返せるかを確認します。

app.e2e-spec.ts
  it('/ (Query) GET BLOGS', async () => {
    const result = await request(app.getHttpServer()).post('/').send({
      query: '{ blogs { id title author body createdAt } }',
    });

    expect(result.status).toEqual(200);
    expect(result.body.data.blogs).toEqual([
      {
        id: '8c7cf457-5ffe-44c3-98b3-c529c09e9999',
        title: 'title1',
        author: 'author1',
        body: 'body1',
        createdAt: '2019-01-01T00:00:00.000Z',
      },
    ]);
  });
bff$ npm run test:e2e

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total

:ok: 実装前なので、期待通り失敗 しました

実装

今回はこのような構成を目指しています。

その中のこの部分を作っていきます!

BlogModule

CLIで作成していきます

$ nest g module blog
CREATE src/blog/blog.module.ts (88 bytes)
UPDATE src/app.module.ts (700 bytes)

AppModuleへのインポートまでやってくれます
Moduleでやりたいことはもう無いので次に行きます


この先は単体テストを書いていくので、下の図のような流れで実装していきます。

image.png

BlogService

BlogServiceを作っていきます。

責務

このServiceでは、APIからデータを取得して、BlogJsonに変換し、Resolverに返すことを責務としました

$ nest g service blog
CREATE src/blog/blog.service.spec.ts (446 bytes)
CREATE src/blog/blog.service.ts (88 bytes)
UPDATE src/blog/blog.module.ts (155 bytes)

単体テスト用のSpecファイルと、BlogModuleへのインポートまでやってくれます(便利!!) 👍

テスト

工夫したポイント
axiosのgetメソッドをモックして、思った結果が返ってくるかを確認しました

blog.service.spec.ts
  it('should return blogs json', async () => {
    const originalAxiosGet = axios.get;
    const response = {
      data: {
        blogs: [
          {
            id: 'id 1',
            title: 'Blog 1',
            author: 'Author 1',
            body: 'Body 1',
            createdAt: '2023-12-01 09:00:00',
          },
          {
            id: 'id 2',
            title: 'Blog 2',
            author: 'Author 2',
            body: 'Body 2',
            createdAt: '2023-12-02 09:00:00',
          },
        ],
      },
    };

    axios.get = jest.fn().mockResolvedValue(response);
    const actual = await service.get();
    const expected = new BlogsJson(response.data);

    expect(actual).toEqual(expected);

    axios.get = originalAxiosGet;
  });
 FAIL  src/blog/blog.service.spec.ts

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.02 s, estimated 2 s

:ok: 実装前なので、期待通り失敗 しました

実装

blog.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';

export class BlogJson {
  constructor(
    public id: string,
    public title: string,
    public author: string,
    public body: string,
    public createdAt: string,
  ) {}
}

export class BlogsJson {
  constructor(data: any) {
    this.blogs = data.blogs.map(
      (d: any) => new BlogJson(d.id, d.title, d.author, d.body, d.createdAt),
    );
  }
  blogs: BlogJson[];
}

@Injectable()
export class BlogService {
  async get(): Promise<BlogsJson> {
    const response = await axios.get('http://localhost:8080/blogs');
    return new BlogsJson(response.data);
  }
}
 PASS  src/blog/blog.service.spec.ts
  BlogResolver
    ✓ should be defined (4 ms)
    ✓ should return blogs json (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.02 s, estimated 2 s

実装したので通りました👍

BlogResolver

最後にBlogResolverを作っていきます。

責務

このResolverでは、BlogServiceから受け取ったBlogJsonオブジェクトをSchemaファイルに合わせたBlogオブジェクトに変換して返す

$ nest g resolver blog
CREATE src/blog/blog.resolver.spec.ts (456 bytes)
CREATE src/blog/blog.resolver.ts (86 bytes)
UPDATE src/blog/blog.module.ts (217 bytes)

単体テスト用のSpecファイルと、BlogModuleへのインポートまでやってくれます(便利!!) 👍

テスト

工夫したポイント
serviceのgetをモックしました

blog.resolver.spec.ts
blog.resolver.spec.ts
  it('should return blogs', async () => {
    const mockBlogService = {} as BlogService;
    mockBlogService.get = jest.fn().mockResolvedValue(
      new BlogsJson({
        blogs: [
          {
            id: 'id 1',
            title: 'Blog 1',
            author: 'Author 1',
            body: 'Body 1',
            createdAt: '2023-12-01 09:00:00',
          },
          {
            id: 'id 2',
            title: 'Blog 2',
            author: 'Author 2',
            body: 'Body 2',
            createdAt: '2023-12-02 09:00:00',
          },
        ],
      }),
    );

    const expected = [
      new Blog('id 1', 'Blog 1', 'Author 1', 'Body 1', '2023-12-01 09:00:00'),
      new Blog('id 2', 'Blog 2', 'Author 2', 'Body 2', '2023-12-02 09:00:00'),
    ];

    const resolver = new BlogResolver(mockBlogService);
    const actual = await resolver.get();

    expect(actual).toEqual(expected);
  });
 FAIL  src/blog/blog.resolver.spec.ts

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.02 s, estimated 2 s

:ok: 実装前なので、期待通り失敗 しました

実装

工夫したポイント
自動生成されるSchemaの型ファイルをimplementした、Blogクラスを作成し、それを返却するようにしました。

blog.resolver.ts
blog.resolver.ts
import { Query, Resolver } from '@nestjs/graphql';
import { BlogService } from './blog.service';
import { Blog as IBlog } from '@/graphql/generated';

export class Blog implements IBlog {
  constructor(
    public id: string,
    public title: string,
    public author: string,
    public body: string,
    public createdAt: string,
  ) {}
}

@Resolver('Blog')
export class BlogResolver {
  constructor(private blogService: BlogService) {}

  @Query('blogs')
  async get(): Promise<Blog[]> {
    const blogsJson = await this.blogService.get();
    return blogsJson.blogs.map(
      (blogJson) =>
        new Blog(
          blogJson.id,
          blogJson.title,
          blogJson.author,
          blogJson.body,
          blogJson.createdAt,
        ),
    );
  }
}
 PASS  src/blog/blog.resolver.spec.ts
  BlogResolver
    ✓ should be defined (4 ms)
    ✓ should return blogs (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.02 s, estimated 2 s

実装したので通りました👍

E2E

最後に環境を立ち上げてE2Eを回してみます

image.png

 PASS  test/app.e2e-spec.ts
  AppController (e2e)
    ✓ / (Query) GET BLOGS (327 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.466 s

通りました👍

これでBFFの実装は終了なので、APIの実装に移ります。
次回はまずRustでのAPI開発の準備を進めていきます

image.png

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?