TypeScriptでExpressのテストを効率化!モックサーバーの構築方法とバックエンド開発初心者向け完全ガイド
生徒
「TypeScriptでWebサーバーのテストをしたいのですが、本物のデータベースや外部のシステムに接続せずにテストする方法はありますか?」
先生
「そういった時には、本物の代わりに偽物のプログラムを身代わりとして用意する、モックという技術を使います。Expressのサーバー環境をモック化することで、安全かつ高速にテストを行うことができますよ。」
生徒
「難しそうですが、パソコン初心者でも自分で作れるようになりますか?」
先生
「大丈夫です!専門用語の解説も含めて、基本的な設定から順番に仕組みを見ていきましょう!」
1. モックサーバーとは何か
Webアプリケーションの開発において、テストという作業は非常に重要です。テストとは、作ったプログラムが正しく動くかどうかを確認する作業のことです。しかし、本物のデータベースや、インターネット上の他のシステムと通信するプログラムをそのままテストすると、様々な問題が発生します。例えば、テストを実行するたびにデータベースのデータが書き換わってしまったり、通信相手のシステムがメンテナンスで止まっているとお手元のテストも失敗してしまったりします。
そこで登場するのがモックという仕組みです。モックとは、一言で言うと模型や試作品、あるいは偽物の身代わりプログラムのことです。本物のプログラムとそっくりな動きをする偽物を一時的に用意することで、周囲の環境に左右されることなく、自分の作ったプログラムの動きだけに集中して正しいか検証できるようになります。今回の解説では、バックエンド開発でよく使われるExpressという仕組みを使ったサーバーの動きをモック化する方法を学びます。これにより、パソコンの動作が重くなることもなく、何度でも安全にテストを繰り返すことが可能になります。
2. バックエンド開発とExpressの基礎知識
初心者の方のために、まずはバックエンドという言葉とExpressについて整理しておきましょう。私たちが普段スマートフォンやパソコンでインターネットのWebサイトを見るとき、画面にボタンが表示されたり文字が綺麗に配置されたりしています。この目に見える画面の部分をフロントエンドと呼びます。これに対して、画面の裏側、つまり見えないところでデータを保存したり計算したりする仕組みのことをバックエンド、またはサーバーサイドと呼びます。
このバックエンドを効率よく開発するための道具として、世界中で広く使われているのがExpressという仕組みです。Expressは、Node.jsという環境の上で動作するWebサーバーの土台です。これを使うことで、特定のURLにアクセスがあったときに、どのようなデータを返すかというルールを簡単に決めることができます。そして、このExpressのプログラムをより安全に、間違いが少ないように書くために導入される言語がTypeScriptです。TypeScriptは、データに型という名札をつけることで、書き間違いをパソコンが事前に教えてくれる親切なプログラミング言語です。
3. テスト環境に必要なJestとSupertestの役割
TypeScriptとExpressを使ったバックエンド開発でテストを行うには、専用の補助ツールが必要です。今回は、プログラムのテストを自動で実行してくれるJestという道具と、Expressで作ったサーバーに対して実際に通信のテストを仕掛けてくれるSupertestという道具を組み合わせて使用します。
Jestは、私たちが書いたプログラムの結果が、予測通りの答えになっているかを自動的に判定してくれる司令塔のような存在です。そしてSupertestは、わざわざブラウザを起動してURLを入力しなくても、プログラムの中で仮想的にサーバーへデータを送信し、返ってきたお返事の内容を確認できる便利な配達員のような役割を持っています。この二つの道具を組み合わせることで、Expressのサーバーをモック化し、本物のネットワーク通信を行わずに一瞬でテストを完了させることができるようになります。これにより、開発の効率が劇的に向上します。
4. 最もシンプルなExpressサーバーの基本コード
まずはモック化を行う前の準備として、テストの対象となる簡単なExpressサーバーのプログラムを書いてみましょう。このプログラムは、特定のURLにアクセスすると、歓迎のメッセージを文字として返すだけのシンプルな仕組みです。プログラムの書き方を一つずつ確認していきましょう。
import express from 'express';
const app = express();
app.get('/api/welcome', (req, res) => {
res.status(200).json({ message: 'いらっしゃいませ!ゲストさん' });
});
export default app;
このプログラムでは、まず一行目でExpressの機能を読み込んでいます。次にappという名前のサーバーの本体を作り、/api/welcomeというURLにアクセスが来た場合の処理を登録しています。res.status(200)というのは、通信が成功したという意味の正常信号を指しています。そして、最後に画面に対して文字のデータを返しています。このプログラムを基準にして、次からの章でテストとモックの具体的な書き方を解説していきます。
5. 外部通信を遮断してモック化する最初のテスト
それでは、先ほど作成したサーバーのプログラムに対して、モックの考え方を取り入れた最初のテストコードを書いてみましょう。ここでは、実際にインターネットを通じてサーバーを起動するのではなく、Supertestの機能を使って内部的にサーバーを呼び出し、正しいお返事が返ってくるかどうかを検証します。これがモックテストの基本形となります。
import request from 'supertest';
import app from './app';
describe('ウェルカム通信のテスト', () => {
it('正しく歓迎メッセージが返ってくること', async () => {
const response = await request(app).get('/api/welcome');
expect(response.status).toBe(200);
expect(response.body.message).toBe('いらっしゃいませ!ゲストさん');
});
});
このコードの中にあるdescribeやitという単語は、テストのまとまりや、何を確かめたいのかという目的を人間の言葉で書いておくための枠組みです。request(app).get()という部分が、本物のブラウザの代わりにサーバーへアクセスを試みている場所になります。そして、expectという命令を使って、返ってきた信号が200番であること、またメッセージの内容が一致していることを確認しています。このテストを実行すると、次のような結果が得られます。
ウェルカム通信のテスト
✓ 正しく歓迎メッセージが返ってくること (15ms)
6. データベース処理を身代わりに差し替える応用モック
実際のバックエンド開発では、ただ文字を返すだけでなく、データベースからユーザーの情報を探してくるような複雑な処理が必ず入ってきます。このようなときこそ、モックの真価が発揮されます。今回は、データベースから名前を検索する処理を完全に偽物のデータに差し替えて、サーバーのテストを行う方法を説明します。
import request from 'supertest';
import app from './app';
jest.mock('./database', () => {
return {
getUserName: jest.fn().mockResolvedValue('山田太郎')
};
});
describe('ユーザー情報取得のテスト', () => {
it('モック化されたユーザー名が返ってくること', async () => {
const response = await request(app).get('/api/user');
expect(response.status).toBe(200);
expect(response.body.name).toBe('山田太郎');
});
});
このプログラムで最も重要な部分は、jest.mockと書かれている一行です。これは、データベースに接続するための本物のファイルを指定し、その中身を丸ごと右側の偽物の処理に入れ替えるという強力な命令です。jest.fn().mockResolvedValue()という記述を使うことで、本物のデータベースを開くことなく、あらかじめ決めておいた「山田太郎」という文字を強制的に返却させることができます。これにより、パソコンの中にデータベースが用意されていなくても、サーバーのプログラムが正しくデータを処理できているかどうかのテストが可能になります。
実行した結果は以下のようになり、データベース通信を行わずにテストを無事に通過させることができます。
ユーザー情報取得のテスト
✓ モック化されたユーザー名が返ってくること (12ms)
7. 通信エラーの状態を意図的に作り出すモックの活用
モックを使用する大きなメリットの一つに、本物の環境では滅多に起きないトラブルやエラーの状態を、自分の手で自由につくり出せるという点があります。例えば、通信回線が突然切れてしまった場合や、サーバーの内部で深刻なエラーが発生した場合に、システムがクラッシュせずに正しいエラー画面を表示できるかどうかは、非常に重要なチェック項目です。モックを使えば、そのような特殊な状況も一行で再現できます。
import request from 'supertest';
import app from './app';
jest.mock('./database', () => {
return {
getUserName: jest.fn().mockRejectedValue(new Error('通信に失敗しました'))
};
});
describe('エラー発生時のテスト', () => {
it('エラーが発生したときに500番の不具合信号を返すこと', async () => {
const response = await request(app).get('/api/user');
expect(response.status).toBe(500);
expect(response.body.error).toBe('通信に失敗しました');
});
});
このコードでは、先ほどとは異なりmockRejectedValueという命令を使用しています。これは、わざと処理を大失敗させて、エラーを発生させるための設定です。本物のデータベースを故障させることは困難ですが、このモックの技術を使えば、プログラムの内部だけで安全に大失敗の状態を作り出すことができます。そして、サーバーがその不具合を上手に検知し、最終的に500番というエラー信号とお詫びのメッセージを返せているかどうかを、確実にテストすることができます。
エラー発生時のテスト
✓ エラーが発生したときに500番の不具合信号を返すこと (8ms)
8. 初心者がモックテストで気をつけるべき注意点
ここまで見てきたように、モックは非常に便利で強力な道具ですが、使い方を誤ると落とし穴にはまってしまうこともあります。一番注意しなければならないのは、偽物の設定を作り込みすぎて、本物のプログラムの動きからかけ離れてしまうことです。モックはあくまでも自分の作った仮定の上で動いているため、モックのテストがどれだけ成功しても、本物のデータベースの書き方が間違っていれば、実際のアプリケーションは動きません。
そのため、開発の全体的な流れとしては、まずはこのモックサーバーを使ったテストで個々のプログラムの部品が正しく動くかを確認し、開発の最終段階では、必ず本物のデータベースやシステムを繋ぎ合わせた全体の確認を行うことが推奨されます。それぞれのテストの役割をしっかりと理解し、適切に使い分けることが、優秀なバックエンド開発者への第一歩となります。この感覚を掴むために、まずは小さなプログラムから何度もモックを書いて練習を重ねていきましょう。