ここのところ旧世代への変革記事が続いているが、今回は新聞だ。敢えて地方紙である神戸新聞を取ることにした。
私はこれまでもネットニュースは見ていたのだが内容の偏りや、興味のない情報が多く悩みを抱えていた。特に私は兵庫県や神戸市の情報を重視したかったので、今年あたりから神戸新聞NEXTの無料版を利用していたのだが、読めない記事が多く困っていたので去る10月に神戸新聞NEXTの購読契約をしたのだが、どうにも情報量が少ないと感じた。主要なニュースはあるのだが、それ以外が薄いというか。
そこで思い浮かんだのが紙面購読だ。紙面であれば漏れなく全ての情報があるし、毎朝・夕届くので生活習慣をつけるのにもいい。毎朝ポストまで取りに行くのは良い習慣になるだろうというのと、紙のメディアを読むというのは新鮮な体験になるだろうと思い、まずは試読を申し込んだ。実はこれしばらく届かなかったのだが、どうも誤配があったようで一週間後からはちゃんと届くようになった。無論、誤配中の試読はなかったことになり、新しく試読を始めることができたので神戸新聞の対応には感謝したい。
そして予想通り紙の新聞には電子版には書かれていない情報が多く、また画面遷移せずとも一覧的に情報を取り込めるのは非常に良い体験だった。そして新聞記事だけでなく、読者投稿や地域の細かい話、チラシなども見ることができる。情報のチャンネルが自然に増えるというのはとても良いことだ。例えば読者が普段感じていることや、年末年始のごみ収集についても書かれている。特に年末年始のごみ収集の情報を特に意識せずとも入手できるのはとてもありがたい。チラシについてもWeb広告のような不快感をあおるようなものは今のところ届いておらず、地域のスーパーなどの広告がメインだ。胡散臭い霊感商法的なものや、週刊誌的な広告も紙面の端やチラシとして封入されているが、昔からあるものということもあり、個人的には特に目に付くこともない。
紙媒体というか、リアルなアナログ媒体あるあるだと思うのだが、何気ない情報に出会えるのも新聞の醍醐味だと感じる。先ほども書いたことだが一覧性が高いことによって適当にページをめくっているだけでも意外な情報と出会えることがある。これは現実の書店に行ったときに思いがけない本に出会えるのと同じ理屈だろう。私はこのことを現実世界の解像度の高さと呼んでいる。デジタル世界は解像度が低いのだ。画面に表示できる情報は高々知れている。例えVRだとしても現実には到底敵わない。何故なら今のVRの解像度が低いからだ。
今のインターネットには余りにも情報が氾濫しており、その中には誤った情報や、錯誤させることを狙ったような情報、攻撃的な情報も少なくなく、見ていて非常に疲れる。これは私がX(旧Twitter)から身を引き始めた理由の一つでもある。今ではTwitterへの投稿数は最盛期の一割程度にまで落ち込んでいる。普段はログインすらしておらず、アプリも消してできるだけ見ないようにしている。利用するのはもっぱらブラウザ経由で毎回ログインしてはログアウトする運用をしている。
この辺りは、なるべくSNSに依存せずにブログを書いていこうという方針にしている。SNSに張り付いていると疲れるからだ。同様に新聞も朝夕だけ見ればいい。見切れなければ適当な空き時間に読めばいいのだ。時間などいくらでもあるのだから。
最近地方紙は衰退の危機にあるともいう部分もあるし、伝統的な新聞配達というのも続いてほしい気持ちがあるので試読が終わったら定期購読に移ろうと考えている。あと新聞紙があれば態々新聞紙の代わりになる神を買わなくていいのもメリットだろう。紙が欲しくなれば、すぐそこにいくらでもある状況だ。市の広報誌も取っているので新聞紙自体のストックはあるが広報誌の数など知れているので年末の大掃除くらいでしか役に立たない。
勿論、紙の新聞を使うことで貴重な資源やエネルギーの無駄遣い、配達員への負荷が増えるなどあるだろう。新聞配達は過酷な労働だ。人権侵害である可能さえある。しかしだからと言ってなくしていいものか?私は歴史と伝統は守られたほうが良いと考えているため、一概に西洋の価値観を肯定する気はなく、日本の歴史や文化を尊び、守っていくために大切にしていきたいと思っている。
果たして紙の新聞がなくなり、新聞のコモディティ化により地方紙も消えた世界が本当にいい世界なのかどうか、それは誰にとってどういい世界なのか、そこを考える価値はあると私は思っている。地方紙が消えれば次に消えるのは全国紙だ。世界各国から新聞会社が消え、NEWYORK TIMESだけになった世界というのも、今後を考えれば十分にありうる未来の一つだろう。私はそれを良しとしない。勿論、そんな極端なことになるかどうかは今はわからないが、少なくとも地方紙が風前の灯火であるというのは事実だろうから、自分なりに支えていきたいし、自分の精神の健康や、文化的生活の糧としても利用していきたいところだと考えている。
毎回忘れるので書いておく
基本は以下の動画の通りでよいが参考程度に途中の写真も置いておく
弦はペグに向けて通し、ペグの反対側を折り曲げる
動画の通りに一回目の巻きは上を通し、以降の巻きは下を通すようにする
最終形態はこんな感じ。飛び出た弦は危ないので切ってある。別に動画のように上に持ち上げてもいいと思うが、購入時点では切ってあったのと安全上の理由もあり、私は切り落としている
余談だが椅子の上に乗せてやると作業しやすい
Athenaクエリ周りのメモ。
Athena前提知識
Amazon Athena とはによると、次のようにあるが、癖があることは覚えていた方が良い。
Amazon Athena は、標準的な SQL を使用して Amazon Simple Storage Service (Amazon S3) 内のデータを直接分析することを容易にするインタラクティブなクエリサービスです。
関数についてはAmazon Athena の関数を見るとよいのだが、Prestoのサイトを見たほうが多分早い。
UTCのISO日付書式文字列をJSTロケールのtimestampに変換する
SELECT
date_parse('2020-01-05T23:21:00.53Z', '%Y-%m-%dT%H:%i:%s.%fZ') AT TIME ZONE 'Asia/Tokyo' AS jst
timestampをdate型に変換する
SELECT
CAST(TBL.TSTMP AS date)
FROM (
SELECT timestamp '2020-11-20 01:00:00' AS TSTMP
) TBL
TO_DATEはtimestampを受け付けないので役に立たない。TO_CHARはドキュメント上扱えるように見えるがフォーマット文字でエラーになる。
以下の様にしてもサポート外の数値型が渡る様で上手くいかない
TO_CHAR(year(TBL.TSTMP)) || '-' || TO_CHAR(month(TBL.TSTMP)) || '-' || TO_CHAR(day_of_month(TBL.TSTMP))
date型をWHEREで比較
date YYYY-MM-DD
として、型を明示しないと上手くいかない
SELECT
*
FROM
TBL
WHERE
TBL.createAt = date '2023-11-22'
以前記事にした、加古川総合文化センターにて行われた「いとうのいぢ展 ぜんぶ!」で注文した版画が遂に到着した。
これは私にとっては初の版画となる。版画といえば、今から20年かそのくらい前には版画屋という悪質商人がいるという噂で良いイメージがなかったが、今ではそういう話も聞かなくなった気がするのできっと良い世界になったのだと思う。個人的には版画自体は職業イラストレーターの貴重な収入源の一つになっていると考えている。
まぁ、前振りは程々にしておき、こちらが今回届いた版画だ。プチプチで二重巻きにされてた上に、角には段ボールが嵌めてあり、更に額縁そのものは段ボールで包装されており、更にその中もビニールで保護されているという、非常に厳重に作品が保護されていて、素直に凄いと思った。まだこの写真の時点ではビニールで包装された状態だ。
しかし買う前のサンプルで解っていたことだが、やはり加古川市章はない。
これはビニール包装を解いた裏側だ。ぶら下げる金具の様なものと謎の紐が見えるが、これはぶら下げる金具ではなく紐通しらしい。
この紐を使ってフックなどにぶら下げて飾る用途のようだ。よく見たら作品に添付されていた説明書にもそう書かれていた。作品を飾るのにちょうどいい塩梅で設定されているらしく、解かないほうがいいらしい。ただ金具類は調子に応じて調整してとあったので、左下の緩んでいる金具だけドライバーで締めておいた。
作品保証書までついていた。エンボス加工がされており、高級感があるが、恐らくは偽造防止用だろう。エディションナンバーまで刻まれていて、これが私の元に届いたものであることを正に証明している。
ひとまず撮影台の上に置いておいても仕方がないので飾ってみることにした。フックはあるのだが、程よい高さに丁度いいスペースがないのと、立てかけていた方が額縁らしい気がしたので壁に立てかけてみた。部屋の明かりの関係で反射するため、暗めの写真になってしまったがいい感じだ。下に並んでいるのはアートタイルで、こちらは会場で直接購入したものだ。版画は会場での直接販売がなかったので、会場での予約販売を利用した。
因みに版画とは言うが、いわゆる木彫りの板にインクを塗って紙に押し付けるようなものではなく、特殊印刷と呼ばれるものである。例えばこの作品には88グラフという印刷技法が使われている。一般的な版画作品はシルクスクリーンという技法がベースになっていると聞くが、こちらも例にもれずシルクスクリーンを利用しているようだ。
大和グランド株式会社の説明によるとシルクスクリーン印刷は一般商業印刷ではほとんど用いられず、特殊な印刷か、軽印刷の分野で多用されているとのことだった。ステッカー、Tシャツ、看板、計器版、プリント回路などの印刷に用いられるケースが多いようである。印刷対象や塗料の種類が豊富なのが強みらしいので、複合的な印刷技術を用いる版画向きの手法なのかもしれない。
ただ今回の版画はアールジュネスで売られているものと比べると幾分か安く見えたので、廉価な印刷技法を使うなど、どこかでコストカットを行っているのだろうか?そこは少しだけ気になった。確か桁が違ったような…。
私の部屋には他にもポスターやタペストリーといった美術品があるが、この版画はそれらとは一線を画した美術品だと思うので、墓に入るまで持ち続けていたいものである。何よりこれは兵庫県の加古川市をモチーフにしたいとうのいぢ先生の作品だ。神戸在住の私としても加古川の宝の一つと言える。
因みにこのキャラクターの名前はかこのちゃんに内定している。なお加古川市章を身に着けたかこのちゃんのフルサイズイラストが見れるのは私が知りうる限り、加古川市のサイトや、加古川市の関連施設のみで、恐らく商業的なグッズ化などはされていない。
JestからNode.js組み込みのテストランナーに移行する時の方法や注意点をまとめたメモ
- 確認環境
- Node.jsの組み込みテストランナーとは
- Jest関数との対応リスト
describe(name, fn)
,it(name, fn, timeout?)
,test(name, fn, timeout?)
beforeAll(fn, timeout?)
,afterAll(fn, timeout?)
,beforeEach(fn, timeout?)
,afterEach(fn, timeout?)
.toBe(value)
,.not.toBe(value)
.toStrictEqual(value)
,.not.toStrictEqual(value)
.toHaveLength(number)
,.toBeNull()
,.toBeUndefined()
,.toBeNaN()
.toBeTruthy()
.toBeInstanceOf(Class)
.toThrow
,not.toThrow
expect.arrayContaining(array)
,expect.objectContaining(object)
,expect.stringContaining(string)
jest.spyOn(object, methodName, accessType?)
.toHaveBeenCalled()
,.toHaveBeenCalledTimes(number)
.toHaveBeenCalledWith(arg1, arg2, ...)
jest.fn(implementation?)
jest.useFakeTimers(fakeTimersConfig?)
,jest.runAllTimers()
jest.useRealTimers()
mockFn.mockClear()
mockFn.mockReset()
- モック機能についての備考
確認環境
Env | ver |
---|---|
Node.js | 20.8.0 |
Jest | 29.7 |
Node.jsの組み込みテストランナーとは
英語圏ではNode.js built-in test runnerとか呼ばれている存在で、Node.jsの組み込みテストランナーのこと。恐らくDenoのテストランナーに対抗して生まれた気配がする(Node.jsはDenoに機能追加があると真似する傾向があるため)
Node.jsの組み込みテストランナーは機能が二分されており、テストランナーとアサートに分かれている。Jestみたいにエコシステムが発達しておらず、標準ではdescribe
などはimportする必要がある。TypeScriptをテストする場合は間にレジスター[^1]を嚙ます必要がある。噛まし方はswc/jestでESM, CJS混在のコードをJestを使ってテストする有力な方法は今のところ多分ないに書いた。
Jest関数との対応リスト
describe, it, beforeAll, afterAll, beforeEach, afterEach辺りは違和感がなかったが、それ以外は軒並み互換性がないので大きく書き換えが必要だと感じた。便利なMatcherは完膚なきまでに全滅している。お陰で覚えることが減ったのは逆に良くなったと感じる
なお、not始まりの機能は実際の動作を確認しておらず、Jestと同じ機能かどうかは確認していないことに留意すること
describe(name, fn)
, it(name, fn, timeout?)
, test(name, fn, timeout?)
Node.jsのTest機能では、以下となる
describe([name][, options][, fn])
it([name][, options][, fn])
test([name][, options][, fn])
基本的な差はないがJestにあったtimeout
引数はNode.jsではoptions
パラメーターで設定するようになっている模様。今回調べるまで存在自体を知らなかったのもあり、このtimeout
がJestと同じ機能かどうかは確認していない
import { describe, it } from 'node:test';
describe('hoge', () => {
it('hoge', () => {
// ここにテストコード
});
it.todo('piyo');
});
beforeAll(fn, timeout?)
, afterAll(fn, timeout?)
, beforeEach(fn, timeout?)
, afterEach(fn, timeout?)
Node.jsのTest機能では、以下となる
before([fn][, options])
after([fn][, options])
beforeEach([fn][, options])
afterEach([fn][, options])
基本的な差はないがJestにあったtimeout
引数はNode.jsではoptions
パラメーターで設定するようになっている模様。今回調べるまで存在自体を知らなかったのもあり、このtimeout
がJestと同じ機能かどうかは確認していない
+import { after, before, beforeEach, afterEach, describe } from 'node:test';
+
describe('test', () => {
- beforeAll(() => {
+ before(() => {
console.log('before');
});
beforeEach(() => {
console.log('beforeEach');
});
afterEach(() => {
console.log('afterEach');
});
- afterAll(() => {
+ after(() => {
console.log('after');
});
});
.toBe(value)
, .not.toBe(value)
Node.jsのTest機能では、以下となる
assert.strictEqual(actual, expected[, message])
assert.notStrictEqual(actual, expected[, message])
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
const actual = 123;
- expect(actual).toBe(123);
+ assert.deepStrictEqual(actual, 123);
});
});
.toStrictEqual(value)
, .not.toStrictEqual(value)
Node.jsのTest機能では、以下となる
assert.deepStrictEqual(actual, expected[, message])
assert.notDeepStrictEqual(actual, expected[, message])
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
const actual = {
hoge: {
piyo: {
id: 1,
value: 'one'
}
},
fuga: [[123, 456], 'ABC', 'EFG']
};
- expect(actual).toStrictEqual({
+ assert.deepStrictEqual(actual, {
hoge: {
piyo: {
id: 1,
value: 'one'
}
},
fuga: [[123, 456], 'ABC', 'EFG']
});
});
});
.toHaveLength(number)
, .toBeNull()
, .toBeUndefined()
, .toBeNaN()
Node.jsのTest機能では、assert.strictEqual()
となる
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
const arr = [1, 2, 3];
const undef = undefined;
const nil = null;
const nan = NaN;
- expect(arr).toHaveLength(3);
- expect(undef).toBeUndefined();
- expect(nil).toBeNull();
- expect(nan).toBeNaN();
+ assert.strictEqual(arr.length, 3);
+ assert.strictEqual(undef, undefined);
+ assert.strictEqual(nil, null);
+ assert.strictEqual(nan, NaN);
});
});
.toBeTruthy()
Node.jsのTest機能では、以下となる
assert.ok(value[, message])
否定版は不明
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
- expect(true).toBeTruthy();
- expect({}).toBeTruthy();
- expect([]).toBeTruthy();
- expect(42).toBeTruthy();
- expect('0').toBeTruthy();
- expect('false').toBeTruthy();
- expect(new Date()).toBeTruthy();
- expect(-42).toBeTruthy();
- expect(12n).toBeTruthy();
- expect(3.14).toBeTruthy();
- expect(Infinity).toBeTruthy();
- expect(-Infinity).toBeTruthy();
+ assert.ok(true);
+ assert.ok({});
+ assert.ok([]);
+ assert.ok(42);
+ assert.ok('0');
+ assert.ok('false');
+ assert.ok(new Date());
+ assert.ok(-42);
+ assert.ok(12n);
+ assert.ok(3.14);
+ assert.ok(Infinity);
+ assert.ok(-Infinity);
});
});
.toBeInstanceOf(Class)
Node.jsのTest機能では、assert.ok()
となる
結果がTruthyであればなんでもpassするので注意
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
const actual = new Error('hoge');
- expect(actual).toBeInstanceOf(Error);
+ assert.ok(actual instanceof Error);
});
});
.toThrow
, not.toThrow
Node.jsのTest機能では、以下となる
assert.throws(fn[, error][, message])
assert.doesNotThrow(fn[, error][, message])
Jestより便利になっており、Error型以外も扱えるので後述する
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
const err = new Error('test');
- expect(() => {
+ assert.throws(() => {
throw err;
- }).toThrow(err);
+ }, err);
});
});
Node.jsではJestと異なりError型以外も扱える
Jestの`.toThrow()
はError型以外を扱うことができず、オブジェクトをThrowするようなコードではエラーになる
describe('test', () => {
it('test', () => {
const obj = { id: 1, value: 'hoge' };
// このテストは失敗する。またTypeScriptの型エラーにもなる
expect(() => {
throw obj;
}).toThrow(obj);
// このテストは失敗する。またTypeScriptの型エラーにもなる
expect(() => {
throw obj;
}).toThrow({ id: 1, value: 'hoge' });
});
});
しかしNode.jsであればこれはエラーにならない。例えば次のテストコードは成功する
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
describe('test', () => {
it('test', () => {
const obj = { id: 1, value: 'hoge' };
assert.throws(() => {
throw obj;
}, obj);
assert.throws(
() => {
throw obj;
},
{ id: 1, value: 'hoge' }
);
});
});
expect.arrayContaining(array)
, expect.objectContaining(object)
, expect.stringContaining(string)
恐らく非対称マッチャーはないので自分でロジックを書いてassert.strictEqual()
で判定するしかないと思われる。元々微妙な機能だったのでやむなし
jest.spyOn(object, methodName, accessType?)
Node.jsのTest機能では、node:test
からmock
をimportして以下を使う
mock.method(object, methodName[, implementation][, options])
+import { describe, it, mock } from 'node:test';
+
describe('test', () => {
it('test', () => {
- jest.spyOn(console, 'log');
+ mock.method(console, 'log');
});
});
本来の挙動を塞ぎたい場合
例えば以下のようにテストコードを書いた場合、execSync()
が実際の挙動で動作してしまい、テストとして機能しない。
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';
const mockedExecSync = mock.method(child_process, 'execSync');
describe('execSync', () => {
it('execSyncが正しい引数で呼ばれること', () => {
execSync('false');
assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
});
});
このような場合、以下のようにmock.method()
の第三引数を指定してやると封じることができる。単体テストの観点では基本的に第三引数には空関数を入れておくのが望ましいだろう。
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';
- const mockedExecSync = mock.method(child_process, 'execSync');
+ const mockedExecSync = mock.method(child_process, 'execSync', () => {});
describe('execSync', () => {
it('execSyncが正しい引数で呼ばれること', () => {
execSync('false');
assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
});
});
モックパターン
module.exports
されている関数のモック
import foo from 'foo';
形式でmock.method()
の第一引数を埋める
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';
const mockedExecSync = mock.method(child_process, 'execSync', () => {});
describe('execSync', () => {
it('execSyncが正しい引数で呼ばれること', () => {
execSync('false');
assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
});
});
上記が正常に動作することはNode.js v20.0.0時点のコードで確認している
Global objectから生えている関数のモック
以下のようにmock.method()
の第一引数にGlobal objectを設定すればよい
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
describe('exit', () => {
// exitが実際に走って落ちるのでmock.methodの第三引数を指定している
const mockedExit = mock.method(process, 'exit', () => {});
it('call exit', () => {
process.exit(1);
assert.strictEqual(mockedExit.mock.calls.length, 1);
});
});
Named exportされている関数のモックは今のところ無理そう
ファイルモックをする手段がないので正攻法では無理そう
https://github.com/nodejs/help/issues/4298
2024-10-15追記
Node.js v22.3.0でテスト走行時に--experimental-test-module-mocks
を渡すことで近いことができるようになった模様だが、試したところ上手く動かないし、spy的な使い方はできなさそうだ。mock.method()との組み合わせも試してみたが、上手くいかなかった。
ObjectやNamespaceでラップされている関数のモック
実装例(Object)
export const Hoge = {
validateFoo() {
// 例外を飛ばす可能性のある何かの処理
},
hoge() {
Hoge.validateFoo();
return 1;
},
};
実装例(Namespace)
export namespace Hoge {
export const validateFoo = () => {
// 例外を飛ばす可能性のある何かの処理
};
export const hoge = () => {
validateFoo();
return 1;
};
}
実装例に対するテストコード
ObjectもNamespaceも同じ書き方でテスト可能
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { Hoge } from './hoge';
describe('hoge', () => {
it('validateFooが例外をスローした場合、例外がスローされること', (t) => {
t.mock.method(Hoge, 'validateFoo', () => {
throw new Error('foo');
});
assert.throws(() => {
Hoge.hoge();
}, Error('foo'));
});
it('全ての関数が正常終了した場合、戻り値を返すこと', () => {
const actual = Hoge.hoge();
assert.strictEqual(actual, 1);
});
});
.toHaveBeenCalled()
, .toHaveBeenCalledTimes(number)
Node.jsのTest機能では、assert.strictEqual()
でモックから生えてるやつを調べる。returnも同様の手法で実現できる
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
- const spiedConsoleLog = jest.spyOn(console, 'log');
+ const mockedConsoleLog = mock.method(console, 'log');
console.log();
- expect(spiedConsoleLog).toHaveBeenCalled();
- expect(spiedConsoleLog).toHaveBeenCalledTimes(1);
+ assert.deepStrictEqual(mockedConsoleLog.mock.calls.length, 1);
});
});
.toHaveBeenCalledWith(arg1, arg2, ...)
Node.jsのTest機能では、assert.strictEqual()
でモックから生えてるやつを調べる。returnも同様の手法で実現できる。
Jestでは.toEqual()
処理されるがNode.jsの組み込みテストランナーの場合、厳密比較ができるので便利
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
- const spiedConsoleLog = jest.spyOn(console, 'log');
+ const mockedInfo = mock.method(console, 'log');
console.log('test');
- expect(spiedConsoleLog).toHaveBeenCalledWith('test');
+ assert.deepStrictEqual(mockedInfo.mock.calls[0].arguments[0], 'test');
});
});
jest.fn(implementation?)
Node.jsのTest機能では、以下となる
mock.fn([original[, implementation]][, options])
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
+// 動作確認用の関数
const testTarget = (cbParam: string, callbackFn: (param: string) => number) => {
return callbackFn(cbParam);
};
describe('test', () => {
it('test', () => {
- const mockFn = jest.fn((_: string) => {
+ const mockFn = mock.fn((_: string) => {
return 5;
});
const actual = testTarget('hoge', mockFn);
- expect(mockFn).toBeCalledWith('hoge');
- expect(mockFn).toReturnWith(5);
- expect(actual).toBe(5);
+ assert.deepStrictEqual(mockFn.mock.calls[0].arguments[0], 'hoge');
+ assert.deepStrictEqual(mockFn.mock.calls[0].result, 5);
+ assert.deepStrictEqual(actual, 5);
});
});
jest.useFakeTimers(fakeTimersConfig?)
, jest.runAllTimers()
Node.jsのTest機能では、以下となる
mock.timers.enable([timers])
mock.timers.runAll()
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
+describe('test', () => {
- jest.useFakeTimers();
+ mock.timers.enable();
+ it('test', () => {
- const mockFn = jest.fn();
+ const mockFn = mock.fn();
setTimeout(() => {
mockFn();
}, 9999);
- expect(mockFn).not.toHaveBeenCalled();
- jest.runAllTimers();
- expect(mockFn).toHaveBeenCalledTimes(1);
+ assert.deepStrictEqual(mockFn.mock.calls.length, 0);
+ mock.timers.runAll();
+ assert.deepStrictEqual(mockFn.mock.calls.length, 1);
});
});
jest.useRealTimers()
Node.jsのTest機能では、以下となる
mock.timers.reset()
+import { mock } from 'node:test';
-jest.useRealTimers();
+mock.timers.reset();
mockFn.mockClear()
Node.jsのTest機能では、以下となる
ctx.resetCalls()
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
- const mockFn = jest.fn((param: string) => `<${param}>`);
+ const mockFn = mock.fn((param: string) => `<${param}>`);
mockFn('hoge');
- expect(mockFn).toHaveBeenCalledTimes(1);
- expect(mockFn).toReturnWith('<hoge>');
+ assert.deepStrictEqual(mockFn.mock.calls.length, 1);
+ assert.deepStrictEqual(mockFn.mock.calls[0].result, '<hoge>');
- mockFn.mockClear();
+ mockFn.mock.resetCalls();
- expect(mockFn).toHaveBeenCalledTimes(0);
+ assert.deepStrictEqual(mockFn.mock.calls.length, 0);
mockFn('piyo');
- expect(mockFn).toHaveBeenCalledTimes(1);
- expect(mockFn).toReturnWith('<piyo>');
+ assert.deepStrictEqual(mockFn.mock.calls.length, 1);
+ assert.deepStrictEqual(mockFn.mock.calls[0].result, '<piyo>');
});
});
mockFn.mockReset()
Node.jsのTest機能では、以下となる
mockFn.mock.restore()
振る舞いが微妙に違うため後述する
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
describe('test', () => {
it('test', () => {
- const spiedConsoleLog = jest
- .spyOn(console, 'log')
- .mockImplementation((param: any) => {
- return `<${param}>`;
- });
+ const mockedConsoleLog = mock.method(console, 'log', (param: any) => {
+ return `<${param}>`;
+ });
console.log('hoge');
- expect(spiedConsoleLog).toHaveBeenCalledTimes(1);
- expect(spiedConsoleLog).toReturnWith('<hoge>');
+ assert.deepStrictEqual(mockedConsoleLog.mock.calls.length, 1);
+ assert.deepStrictEqual(mockedConsoleLog.mock.calls[0].result, '<hoge>');
- spiedConsoleLog.mockReset();
+ mockedConsoleLog.mock.restore();
});
});
JestとNode.jsでの振る舞いの差異
但しJestとNode.jsのTest機能では微妙に差異がある
例えばJestでは以下の実装が正しくPASSするが
describe('test', () => {
it('test', () => {
const mockFn = jest
.spyOn(console, 'log')
.mockImplementation((param: any) => {
return `<${param}>`;
});
console.log('hoge');
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toReturnWith('<hoge>');
mockFn.mockReset();
expect(mockFn).toHaveBeenCalledTimes(0);
console.log('piyo');
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).not.toHaveBeenCalledWith();
expect(mockFn).toReturnWith(undefined);
});
});
Node.jsで以下の実装を書いても同じように機能しない
import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
describe('test', () => {
it('test', () => {
const mockFn = mock.method(console, 'log', (param: any) => {
return `<${param}>`;
});
console.log('hoge');
assert.deepStrictEqual(mockFn.mock.calls.length, 1);
assert.deepStrictEqual(mockFn.mock.calls[0].result, '<hoge>');
mockFn.mock.resetCalls();
mockFn.mock.restore();
assert.deepStrictEqual(mockFn.mock.calls.length, 0);
console.log('piyo');
// 以降のテストはいずれも落ちる
assert.deepStrictEqual(mockFn.mock.calls.length, 1);
assert.deepStrictEqual(mockFn.mock.calls[0].result, '<piyo>');
});
});
但しモック実装を削除したうえで再度呼び出すという行為には意味がないので、特に問題にはならないと思われる
モック機能についての備考
it([name][, options][, fn])
の第三引数のコールバックの第一引数にはTestContext
が入っており、これを使ってモックすることもできる
これを使う場合、スコープアウトでモックが復元されるため、例えば以下のような関数をテストするときに便利である。
実装
export namespace Hoge {
export const validateFoo = () => {
// 例外を飛ばす可能性のある何かの処理
};
export const validateBar = () => {
// 例外を飛ばす可能性のある何かの処理
};
export const validateBaz = () => {
// 例外を飛ばす可能性のある何かの処理
};
export const hoge = () => {
validateFoo();
validateBar();
validateBaz();
return 1;
};
}
テストコード
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { Hoge } from './hoge';
describe('hoge', () => {
it('validateFooが例外をスローした場合、例外がスローされること', (t) => {
t.mock.method(Hoge, 'validateFoo', () => {
throw new Error('foo');
});
assert.throws(() => {
Hoge.hoge();
}, Error('foo'));
});
it('validateBarが例外をスローした場合、例外がスローされること', (t) => {
t.mock.method(Hoge, 'validateBar', () => {
throw new Error('bar');
});
assert.throws(() => {
Hoge.hoge();
}, Error('bar'));
});
it('validateBazが例外をスローした場合、例外がスローされること', (t) => {
t.mock.method(Hoge, 'validateBaz', () => {
throw new Error('baz');
});
assert.throws(() => {
Hoge.hoge();
}, Error('baz'));
});
it('全ての関数が正常終了した場合、戻り値を返すこと', () => {
const actual = Hoge.hoge();
assert.strictEqual(actual, 1);
});
});