【qp】File API利用箇所をjestでテストする

 9月14日の日報です。今日はしんどかった。

File API 利用箇所のテストを書く

 いま仮に、こんなコードがあるとしてですね。

/**
 * read an image file and return contents
 * @param {File} file a file object
 * @return {Promise<string>} base64 data
 */
export const readFile = async (file: File): Promise<string> => {
  // create reader
  const reader = new FileReader();

  // return promise
  return new Promise<string>((resolve, reject) => {
    // event handler
    reader.onload = (event: ProgressEvent) => {
      // get result
      const target = event.target as FileReader | null;

      // reject if target is blank
      if (!target) {
        return reject(new Error("readFile error: event.target is blank"));
      }

      // return result
      const result = target.result as string;
      resolve(result);
    };

    // read as a data url
    reader.readAsDataURL(file);
  });
};

 テストはどう書くの? というと、こう書きます。v10で実行しているので他だと動かないかも。

import { readFile } from "~/store/uploader";
import fs from "fs";

const getSampleBuffer = (): Buffer => {
  const path = `${__dirname}/test-neko.png`;
  return fs.readFileSync(path);
};

describe("readFile", () => {
  let file: File;

  beforeEach(() => {
    // create file object
    file = new File([getSampleBuffer().buffer], "test-neko.png", {
      type: "image/png"
    });
  });

  it("returns data string (base64 format) which file-reader returns", async () => {
    const result = await readFile(file);
    expect(result).toEqual(
      "data:image/png;base64,iVBOR...5CYII=" // 長すぎ中略
    );
  });
});

 今回はテストファイルと同じ場所にサンプル画像を置いています。こんなやつです。

f:id:tottokotkd:20180914233226p:plain

 再エンコードされていてbase64の値は一致しないかもしれませんが、まあその辺は適当に。

何故これで動くのか

 知らんって感じなのですが、いくつかポイントはあります。

 まず node.js Buffer が Uint8Array を継承していることです (詳しくはここ)1

 そしてUint8Arrayには buffer という読取専用プロパティがあり、ここから ArrayBuffer を取得できます。

Uint8Array.prototype.buffer [読取専用]
Uint8Array オブジェクトによって参照されるArrayBufferを返します。構築時に設定され、読取専用となります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

 このプロパティはもちろん fs で取得した Buffer にもあります。つまり buffer.buffer です。なんか分かりにくいですね… まあいいけど。

 ともかく、ここから ArrayBuffer が手に入ります。そしてこれがそのままFileコンストラクタで使えます。

var myFile = new File(bits, name[, options]);

bits
ArrayBuffer、ArrayBufferView、Blob、DOMString オブジェクト、もしくはこれらが混合したArrayです。これはUTF-8でエンコードされたファイルの内容です。

https://developer.mozilla.org/ja/docs/Web/API/File/File

 つまり、こうです2[buffer.buffer] として配列にする必要があるので注意してください。配列の配列ってなんかワクワクしますよね。

const buffer = fs.readFileSync(path);
new File([buffer.buffer], "test-neko.png", { type: "image/png" });

 あとはこれを渡してやればテスト可能です。最初は FileReader からモックにする必要があるかと思ったのですが、特にそんなことはありませんでした。 File だけ作って渡してやればいいみたいです。

 割と簡単にFile API利用箇所をテスト可能になりました。めでたしめでたし3


  1. v8以前は NodeBuffer を経由していますが、まあ多分きっと大体同じです。

  2. 実際には buffer.buffer ではなく buffer を渡しても(この場合は)動きます。

  3. 調べるのは半日かかりましたが完成したのでセーフ