JavaScript class のプライベート関数/変数のアクセス方法と Node Test runner
JavaScript(es6)
の class
のインスタンスからプライベート関数・変数を取得したい。テストしたいので。
プライベート関数・変数を取得するには class
の constructor
に以下のようにテストのときだけ内部にアクセスできる関数を動的に定義する。
#
から始まるプライベートな変数・関数は this["#variable"]
のように取得できないし、Object.getOwnPropertyDescriptors
とか Object.getOwnPropertyNames
にも無い。__proto__
とか prototype
でも取得できない。他に方法があるのか検索しても分からなかったので仕方なく eval
を使う。eval
を使うこの関数の問題点としては internal("foo-bar")
とか書くと NaN
が返ってきたりする。正しく使わないと eval
の解釈によって意図しないことが起こるかもしれない。
index.js
export default class A { publicDynamicVariable; #privateDynamicVariable; static #privateStaticVariable = "#privateStaticVariable-value"; constructor() { this.#privateDynamicVariable = "#privateDynamicVariable-value"; this.publicDynamicVariable = "publicDynamicVariable-value"; // NODE_TEST が環境変数に定義されている場合に internal 関数を使えるようにする if ( typeof process != "undefined" && process["env"] && process.env["NODE_TEST"] ) { this.internal = (name) => { let prop; // A はクラス名。A.staticMethod のように静的な変数・関数にアクセスする用 const o = { this: this, A: A }; for (const k in o) { try { prop = eval(`${k}.${name}`); } catch (e) { continue; } if (typeof prop != "undefined") { if (typeof prop == "function") { // 関数だったら this か A を bind する return prop.bind(o[k]); } return prop; } } // name がプロパティ名として存在しない場合はエラーをスローする throw new Error( `not defined function or variable in class: internal("${name}")` ); }; } } publicDynamicFunction(a, b, c) { return [this.publicDynamicVariable, a, b, c]; } #privateDynamicFunction(a, b, c) { return [this.#privateDynamicVariable, a, b, c]; } static #privateStaticFunction(a, b, c) { return [A.#privateStaticVariable, a, b, c]; } } // new A する前にこれ書いておけば internal 関数が使えるようになる。 process.env.NODE_TEST = true; const a = new A(); let v; // 以下 internal 関数を使って dynamic/static な public/private 変数関数を取得できる v = a.internal("publicDynamicFunction")("foo", "bar", "baz"); console.log(v); // [ 'publicDynamicVariable-value', 'foo', 'bar', 'baz' ] v = a.internal("#privateDynamicFunction")("foo", "bar", "baz"); console.log(v); // [ '#privateDynamicVariable-value', 'foo', 'bar', 'baz' ] v = a.internal("#privateStaticFunction")("foo", "bar", "baz"); console.log(v); // [ '#privateStaticVariable-value', 'foo', 'bar', 'baz' ] v = a.internal("publicDynamicVariable"); console.log(v); // publicDynamicVariable-value v = a.internal("#privateDynamicVariable"); console.log(v); // #privateDynamicVariable-value v = a.internal("#privateStaticVariable"); console.log(v); // #privateStaticVariable-value
Node Test runner を使ったテスト。npmパッケージを一切使ってないコードは、これを使ってテストを書けば依存が一切ないので良い。
ディレクトリ構成
$ tree . ├── index.js ├── index.test.js └── package.json
package.json
の中身。export/import
するために "type": "module"
を書いておく
$ cat package.json { "type": "module" }
テストファイル。assertのドキュメントはここ
index.test.js
import assert from "node:assert/strict"; import { test, describe } from "node:test"; import A from "./index.js"; // internal 関数を使えるようにする process.env.NODE_TEST = true; describe("internal 関数のテスト", () => { const a = new A(); test("不明なプロパティ名はエラーがスローされる", () => { assert.throws(() => a.internal("#unknown"), { message: `not defined function or variable in class: internal("#unknown")`, }); }); // それぞれの変数・関数が正しく取得できてるかどうかのテスト [ { name: "publicDynamicFunction", args: [10, 20, 30], expected: ["publicDynamicVariable-value", 10, 20, 30], }, { name: "#privateDynamicFunction", args: [10, 20, 30], expected: ["#privateDynamicVariable-value", 10, 20, 30], }, { name: "#privateStaticFunction", args: [10, 20, 30], expected: ["#privateStaticVariable-value", 10, 20, 30], }, { name: "publicDynamicVariable", expected: "publicDynamicVariable-value", }, { name: "#privateDynamicVariable", expected: "#privateDynamicVariable-value", }, { name: "#privateStaticVariable", expected: "#privateStaticVariable-value", }, ].forEach((data, index) => { test(`${index}: Test ${data.name}`, () => { const v = a.internal(data.name); const actual = typeof v == "function" ? v(...data.args) : v; assert.deepStrictEqual(actual, data.expected); }); }); });
テストの実行と結果
$ node --test ▶ internal 関数のテスト ✔ 不明なプロパティ名はエラーがスローされる (1.4184ms) ✔ 0: Test publicDynamicFunction (0.6888ms) ✔ 1: Test #privateDynamicFunction (0.1795ms) ✔ 2: Test #privateStaticFunction (1.2162ms) ✔ 3: Test publicDynamicVariable (0.2144ms) ✔ 4: Test #privateDynamicVariable (0.1314ms) ✔ 5: Test #privateStaticVariable (0.1674ms) ▶ internal 関数のテスト (7.1375ms) ℹ tests 7 ℹ suites 1 ℹ pass 7 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 85.036409