Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8883

mockを使ったNode.js(Express)のAPIテスト

$
0
0
はじめに Jest・SupertestでAPIテストを作成した際に躓いたので、その実装の備忘録を残しておく。 この記事では時に、より実践的なテストにするためにはmockを用いるべきであるがどのようにmock化してWeb APIがcallされないようにするか?という部分について書き残す。 ※mockを用いず、ただ単にnode.js(Express)のAPIテスト(End Pointテスト)を実行できるようするための方法は、Jest・Supertestを使用したnode.js(Express)のAPIテスト app.listen()はエラーになるを参照。 以下で実装したテストの実行結果(GitHub Actionsの結果)は以下。 https://github.com/yuta-katayama-23/travel-app/runs/2757301572 なぜmock化する必要があるか? Node.js(Express)でServer Sideを構築する場合、外部のWeb APIを実行しデータを取得することも多くある。その際に、UnitTestやAPIテストで実際に通信を行ってしまうと、テストとして動作が不安定になるなどの問題が発生してしまうため。 詳細はこちらを参照。 外部のWeb APIの実行をmock化してAPIテストする ここで言うAPIテストはEnd Pointテストで、実際に期待されるEnd Pointがありそこから結果が返ってくるか?をテストする。 (実際には以下で取り上げるテスト内容もテストしているとも言えるが・・・。) この場合、Node.jsのmoduleをmock化し外部のWeb APIを実行させないようにする+supertestを用いてテストを実装できる。 テスト対象のコードは以下。 app.js const express = require('express'); const app = express(); /* Middleware */ app.use(express.urlencoded({ extended: false })); app.use(express.json()); // Cors for cross origin allowance const cors = require('cors'); app.use(cors()); // Initialize the main project folder app.use(express.static('dist')) // dotenv const dotenv = require('dotenv') dotenv.config(); const axios = require('axios').default; // config for get countries and cities const axiosConfig = { baseURL: 'https://api.countrystatecity.in/v1/', timeout: 2500, headers: { 'X-CSCAPI-KEY': `${process.env.COUNTRYSTATECITY_API_KRY}` } } app.get('/allCountries', async (req, res) => { try { const countries = await axios.get('countries', axiosConfig) res.status(200).send({ countries: countries.data }) } catch (error) { errorHandler(res, error) } }) const errorHandler = (res, error) => { if (error.response) { res.status(500).send(error.response.data) } else { res.status(500).send({ error: error.message }) } } module.exports = app; テストコードは以下。 test.js const request = require('supertest') const app = require('../../../src/server/app') const axios = require('axios') jest.mock('axios') // ここでNode.jsのmodule(今回はaxios)をmock化している describe('axiosをmock化&supertestでrequestを飛ばしてテスト', () => { it('/allCountries', async () => { const resp = { data: [{ name: 'test' }] }; axios.get.mockResolvedValue(resp); // mock化したmoduleの実行結果を定義している https://jestjs.io/docs/mock-function-api#mockfnmockresolvedvaluevalue const res = await request(app).get('/allCountries') expect(res.status).toEqual(200) expect(res.body.countries[0].name).toEqual('test') }) }) ※テストコードの解説・注意事項  ・jest.mock(module)はrequire/importのスコープに記載する   jest公式にも以下のように書いてある通り、jest.mock(module)はrequire/importのスコープに記載しないとダメ。 Note: In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement. 注: 適切にモックするために、Jest は jest.mock('moduleName') が require/import ステートメントと同じスコープにある必要があります。 route-mock.test.js // 省略 const axios = require('axios') describe('axiosをmock化&supertestでrequestを飛ばしてテスト Get Endpoints (mocking)', () => { it('/allCountries', async () => { jest.mock('axios') // ←このような実装はエラーになる // 省略 }) }) APIのエンドポイントがcallされた時に実行される処理をテスト ここで言うAPIテストはAPIのcall時に実行される処理のテストの事。 End Pintが存在するか?よりはそのEnd Pointをcallした時に実行される処理が期待される挙動であるか?を検証する。 この場合、単純にエイリアス(関数)を呼び出してテストしてあげればよいが、そのテストを実行できるようにNode.js(Express)のソースコードの構成もテストできる形にする必要がある。 プロジェクトの構成のリファクタリング まず、Node.js(Express)の構成だがMVCモデルに則り以下のようにする。 src ・・・ └── server ├── app.js ├── controllers │ └── controller.js ├── routes │ └── router.js └── server └── server.js すると、単純にエイリアス(関数)であるコードを書く事ができるので、テストを実行する際にはこの関数を呼び出すだけでいい。 controller.js const axios = require('axios').default; // dotenv const dotenv = require('dotenv') dotenv.config(); // config for get countries and cities const axiosConfig = { baseURL: 'https://api.countrystatecity.in/v1/', timeout: 2500, headers: { 'X-CSCAPI-KEY': `${process.env.COUNTRYSTATECITY_API_KRY}` } } const allCountries = async (req, res) => { try { const countries = await axios.get('countries', axiosConfig) res.status(200).send({ countries: countries.data }) } catch (error) { errorHandler(res, error) } } const errorHandler = (res, error) => { if (error.response) { res.status(error.response.status).send({ error: error.response.data, errorMsg: error.message }) } else { res.status(500).send({ errorMsg: error.message }) } } module.exports = { allCountries, }; 実際のテスト対象のソースコード全体は以下を参照。 テストコードの実装 上記で関数化できたのでテストコードは以下のようになる。 (テスト対象のコードは上記のallCountries関数。) test.js const { allCountries } = require('../../../src/server/controllers/controller') const axios = require('axios') jest.mock('axios') describe('axiosはmock化&関数のテストとしてテスト', () => { it('/allCountries', async () => { const resp = { data: [{ name: 'not use superttest' }] }; axios.get.mockResolvedValue(resp); const req = {} const res = { status: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis() } await allCountries(req, res) expect(res.status.mock.calls[0][0]).toBe(200) expect(res.send.mock.calls[0][0].countries[0].name).toEqual('not use superttest') }) }) ※テストコードの解説 jest.fn().mockReturnThis()axiosのresponseがres.status().send()というようなメソッドチェーンで使われるのでそれ自身(this)を返してあげる必要があるので.mockReturnThis()でthisを返すようにしている1 mock.calls[0][0]呼び出されたモックに対してmock.calls[n][m]で、n番目の引数に対してm回目の呼び出しで指定された引数を取り出すという意味で、今回は引数1つ&呼び出した回数も1回なので、mock.calls[0][0]に結果が可能されている上記のコードで言えば、res.status(200).send({ countries: countries.data })というテスト対象のコードが、mock化したそれぞれの関数(status: jest.fn()…とsend: jest.fn()…)に対し、・.status(200)で1回呼び出されその時の引数は200なのでres.status.mock.calls[0][0]で200が取り出せる・.send({ countries: countries.data })で1回呼び出されその時の引数は{ countries: countries.data }なのでres.send.mock.calls[0][0]で{ countries: [{ name: 'not use superttest' }] }が取り出せるという事。2 参考文献 https://jestjs.io/docs/manual-mocks#mocking-node-modules https://jestjs.io/ja/docs/mock-functions#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E3%83%A2%E3%83%83%E3%82%AF https://www.agent-grow.com/self20percent/2019/03/25/only-express-and-jest-testing/ https://tech.bitbank.cc/lets-test-by-jest/ https://jestjs.io/ja/docs/mock-functions#%E3%83%A2%E3%83%83%E3%82%AF%E3%81%AE%E5%AE%9F%E8%A3%85 ↩ https://jestjs.io/ja/docs/mock-functions#mock-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3 ↩

Viewing all articles
Browse latest Browse all 8883

Trending Articles