Go でテストを記述する際、モックの生成にはしばしば gomock が使われますが、引数の検証に使う Matcher は標準では限られたもののみが用意されています。

たとえば、以下のテストの 12 行目では gomock.Eq(...) を使って api.Client に対して Get("/v1/info") という呼び出しが 1 回されることを期待しています。

//go:generate mockgen -source=client.go -destination=client_mock.go -package=api

package api

import (
	"io"
	"net/http"
)

type Client interface {
	Get(url string) (*http.Response, error)
	Post(url, contentType string, body io.Reader) (*http.Response, error)
}
import (
	"testing"

	"go.uber.org/mock/gomock"
)

func TestServiceGet(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mock := api.NewMockClient(ctrl)
	mock.EXPECT().Get(gomock.Eq("/v1/info")).Return(nil, nil)
	service := domain.NewService(mock)
	service.DoFancyStuff()
}

このような単純な比較であれば標準の Matcher でも事足りますが、以下のテストの 15 行目のように引数の検証が比較演算でできないケースもあり、gomock.Any() を使って引数の検証自体を省略したままにしてしまうこともあります。

Bash で書かれたシェルスクリプトで引数を処理するためには、ビルトインコマンドの getopts 1が使用できますが、このコマンドは --foo のようなロングオプションをサポートしていません。ロングオプションを GNU の getopt を持たない macOS などの環境を含めてサポートしたい場合は自前で解析する方法や23getopts-: を渡して処理する方法45がありますが、この記事では後者の getopts を使用した方法で、前述のリンク先の手法を参考にしながら、オプションの引数を受けてショートオプションとロングオプションを共通で扱う方法を考えてみます。

ロングオプションの引数

ロングオプションで引数を取るコマンド群には代表的なもので次の種類があります。

--foo=bar: 区切り文字として = を使って受け入れる

--foo=bar のみを受け入れる実装は見当たりませんでした。
この記事の --foo=bar を処理する方法で扱っています。

--foo bar: 引数を分けて受け入れる

FreeBSD tarcurlRubyPython などで採用されている方式です。
この記事の --foo bar を処理する方法で扱っています。

--foo=bar--foo bar の両方を受け入れる

GNU CoreutilsGNU GrepGitNode.jsGNU Awk など多くで採用されている方式です。
この記事の --foo=bar と --foo bar の両方を処理する方法で扱っています。

この記事はもう古くなってしまいました。
Jest v28 以降では、下記のように JSDOM のオプションを指定することができるようになっています。

/**
 * @jest-environment jsdom
 * @jest-environment-options {"url": "https://example.com/"}
 */
it('detects PC', () => {
  expect(location.hostname).toBe('example.com');
});

目的

Node.js におけるテストで Jest を使用する場合、DOM API が関連するテストは JSDOM の実装によって実行されます。この JSDOM はブラウザーと完全に同じ実装を持っているわけではなく、location.href への代入ができないなどの制約があります。そのため Jest でもこれに合わせて JSDOM の API を使用したテストコードを記述する必要があります。

準備

Jest では JSDOM の API を直接参照することができないため、jest-environment-jsdom-global をインストールします。

$ npm i jest-environment-jsdom-global jest-environment-jsdom

package.jsonjest の項目にも追加しておきます。

{
  "jest": {
    "testEnvironment": "jest-environment-jsdom-global"
  }
}

CI などの環境で Node.js の Intl API をはじめとする国際化サポート1が想定した挙動にならない場合があります。たとえば Travis CI の Node.js 環境で以下のコマンドを実行すると ja を指定していても en-US 相当の出力になっています。

$ node -p '(new Intl.DateTimeFormat("ja")).format(new Date())'
3/26/2019

Node.js の i18n は ICU を必要としており2、スクリプト実行の前に full-icu をインストールして環境変数に出しておけば正常に動作します。

$ npm i -g full-icu
export NODE_ICU_DATA=$(node-full-icu-path)

$ node -p '(new Intl.DateTimeFormat("ja")).format(new Date())'
2019/3/26

Travis CI の場合は .travis.ymlbefore_install などに指定しておくと期待した出力が得られます3

環境

Node.js v11.10.0
Travis CI Worker v6.2.0

脚注

2019/02 まで WordPress で管理していた chitoku.jp ですがこの度 Gatsby に移行しました!

TL;DR

  1. Gatsby で TypeScript なら gatsby-plugin-ts-loader + tsconfig-paths-webpack-plugin
  2. Algolia で日本語の検索をするときは Keep diacritics on characters に注意
  3. babel-plugin-react-intl-auto に出した PR がマージされた1
  4. gatsby-transformer-remark に出した PR がマージされた2
  5. remark-grid-tables に出した PR がマージされた3
  6. Gatsby の IE サポートは半分嘘なので残り半分は自分でやる

Gatsby とは

Gatsby は React 製の静的サイトジェネレーターです。Markdown や画像、YAML、テキストファイルなどを透過的に扱うサーバーをローカルに建てて、それらを React のコンポーネントから GraphQL クエリーを通じて組み込めるという特徴があります。豊富なプラグインによって動作を拡張できるほか、細かいカスタマイズは自分でローカルにプラグインとして切り出して管理することができます。

コアの基本的に見える機能もプラグインとして切り出されており、ビルド時のフックに比較的柔軟に介入できます。たとえば Markdown をソースとすると、gatsby-source-filesystem がファイルを読み込んで gatsby-transformer-remark に処理を委譲し、さらに下位のプラグインが Markdown パーサーの処理をカスタマイズできます。今回は以下のようなプラグインを入れてみました。