はじめに
2023年アドベントカレンダー16日目です。
現在「ユーザーはトップページでブログの一覧を見ることができる」を進めています。
前回はGraphQLModuleの設定を行いました。
今回はメインの実装を進めていきます。
E2Eテスト
nest new
でNestJSを作成したときにすでにE2Eテストの概形はできているので、テストを追加していきます。
もっとGraphQLサーバーに特化した書き方があるかもしれません
POSTでqueryを受け取り、期待するレスポンスを返せるかを確認します。
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
実装前なので、期待通り失敗 しました
実装
今回はこのような構成を目指しています。
その中のこの部分を作っていきます!
BlogModule
CLIで作成していきます
$ nest g module blog
CREATE src/blog/blog.module.ts (88 bytes)
UPDATE src/app.module.ts (700 bytes)
AppModuleへのインポートまでやってくれます
Moduleでやりたいことはもう無いので次に行きます
BlogService
BlogServiceを作っていきます。
$ 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
実装前なので、期待通り失敗 しました
実装
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を作っていきます。
$ 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
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
実装前なので、期待通り失敗 しました
実装
工夫したポイント
自動生成されるSchemaの型ファイルをimplementした、Blogクラスを作成し、それを返却するようにしました。
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を回してみます
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開発の準備を進めていきます