kdnakt blog

hello there.

モックサーバを立てて非同期な関数のテストを書く(Jest+TypeScript編)

TypeScriptで利用可能なRSSパーサとして以下のライブラリを利用して、非同期な関数のテストを書いたときのことをまとめておく。

www.npmjs.com

 

 

[やりたいこと]

今回利用したのはrss-parserのv3.8.0である。

rss-parserでは、主に以下の2つの関数が提供されている。

  • parseUrl()
  • parseString()

RSSフィードのURLからパースした結果を得たい場合には前者を、RSSフィードXMLデータからパースした結果を得たい場合には後者を利用することになる。

 

実際のデータとしては、RSSフィードのURLをパースした結果を得たいので、テスト時にはモックサーバを立てて動作確認をしたい。ただし、毎回モックサーバを立ててparseUrl()でテストをするのも時間がかかりそうなので、テスト用にはparseString()の方を利用したい。

 

ということで、以下のようなparse()関数を用意し、これをテストしていく。

import * as Parser from 'rss-parser';

const parser = new Parser();

export const parse = async (feedUrlOrXml: string) => {
  if (!feedUrlOrXml.startsWith("http")) {
    // for testing purpose
    return await parser.parseString(feedUrlOrXml);
  }
  return await parser.parseURL(feedUrlOrXml);
}

 

なお、テスト用のプロジェクトはServerless Frameworkのaws-nodejs-typescriptテンプレートを利用して作成し、以下のサイトを参考にテストフレームワークとしてjestを導入した。 

Jest - TypeScript Deep Dive 日本語版

 

[Jestでの非同期なテストの書き方] 

まずはサーバを立てる前に、parseString()を利用する分岐のテストケースを実装することにした。JavaScript/TypeScriptのテスト普段書き慣れていないため、最初につまづいたのは非同期な関数のテストの実装方法だった。

 

jestの公式サイトによれば、以下の4通りの実装方法があるらしい。

jestjs.io

  • done引数パターン
  • Promiseパターン
  • .resolves/.rejectsパターン
  • async/awaitパターン

 

一番簡単そうなasync/awaitパターンを利用してparseString()のテストを実装すると以下のようになった。

import { parse } from './handler';

const xml = `<!--?xml version="1.0" encoding="UTF-8"?-->
<feed xml:lang="ja-JP" xmlns="http://www.w3.org/2005/Atom">
  <id>dummy-blog</id>
  <entry>
    <title>Hello World!</title>
  </entry>
</feed>`;

test('should parse xml', async () => {
  const output = await parse(xml);
  expect(output).toEqual({
    items: [{
      title: 'Hello World!',
    }]
  });
});

 

[テストでモックサーバを立てる]

本番で利用するparseUrl()関数もテストしておきたい、と思い色々探したところ、rss-parser ライブラリ自体のテストにモックサーバを利用したテストコードがあった。

github.com

 

ただし、こちらはJavaScriptで実装されていたので、TypeScript用に以下のような形に書き換えた。

import { parse } from './handler';
import { createServer } from 'http';

const xml = `<!--?xml version="1.0" encoding="UTF-8"?-->
<feed xml:lang="ja-JP" xmlns="http://www.w3.org/2005/Atom">
  <id>dummy-blog</id>
  <entry>
    <title>Hello World!</title>
  </entry>
</feed>`;

test('should parse url', done => {
  const server = createServer((_, res) => {
    res.write(xml);
    res.end();
  });
  server.listen(async () => {
    let url = 'http://localhost';
    const addr = server.address();
    if (typeof addr !== 'string') {
      url += ':' + addr.port;
    }
    const output = await parse(url);
    expect(output).toEqual({
      items: [{
        title: 'Hello World!',
      }]
    });
    server.close();
    done();
  });
});

 

done()を利用するパターンと、async/awaitを利用するパターンの合わせ技のような感じになっている。

やや詰まった点としては、server.address()の戻り値の方がstring | AddressInfoとなっていて、ストレートにAddressInfo型として扱いポート番号を取得することができなかった。無理やりtypeofで型判定を行うやり方で切り抜けたが、いずれ時間があったらもっとスマートな方法も調べてみたい。

 

[まとめ]

  • JavaScriptRSSパーサライブラリのrss-parserを試した
  • parseString()のテストを実装する上で、jestを利用した非同期テストの実装方法を学んだ
  • parseUrl()のテストを実装する上で、http.createServer()を利用したモックサーバの実装方法を学んだ
  • 今回実装したコードは以下のリポジトリにまとめてある 

github.com