TypeScriptで利用可能なRSSパーサとして以下のライブラリを利用して、非同期な関数のテストを書いたときのことをまとめておく。
[やりたいこと]
今回利用したのは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通りの実装方法があるらしい。
- 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 ライブラリ自体のテストにモックサーバを利用したテストコードがあった。
ただし、こちらは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
で型判定を行うやり方で切り抜けたが、いずれ時間があったらもっとスマートな方法も調べてみたい。
[まとめ]
- JavaScript製RSSパーサライブラリのrss-parserを試した
- parseString()のテストを実装する上で、jestを利用した非同期テストの実装方法を学んだ
- parseUrl()のテストを実装する上で、http.createServer()を利用したモックサーバの実装方法を学んだ
- 今回実装したコードは以下のリポジトリにまとめてある