✍️

ファイルの読み書きを伴うモジュールのDeno.test

2022/04/09に公開

下記の様なts-morphを使用したoverloadを追加する関数のテストを用意してみます。

import { FunctionDeclaration } from "https://deno.land/x/ts_morph@14.0.0/mod.ts";

type Parameter = { name: string; type: string };
type Entry = { parameters: Parameter[] };

export const addCustomEntries = (
  entries: Entry[],
  constructor: FunctionDeclaration
) => {
  constructor.addOverloads(
    entries.map((entry) => ({
      parameters: entry.parameters,
      returnType: "string",
    }))
  );
};

helperの定義

ファイルの読み書きを行うために前後処理を実行するヘルパー関数を定義します。

helper.ts
import {
  Project,
  SourceFile,
  ManipulationSettings,
  IndentationText,
} from "https://deno.land/x/ts_morph@14.0.0/mod.ts";

export const withDir = async (
  cb: (tempDir: string) => void | Promise<void>
) => {
  const tempDir = await Deno.makeTempDir({
    prefix: "deno_couscous_test_",
  });
  await cb(tempDir);
  await Deno.remove(tempDir, { recursive: true });
};

export const withSource = (
  cb: (tempSrc: SourceFile) => void | Promise<void>,
  manipulationSettings: Partial<ManipulationSettings> = {
    indentationText: IndentationText.TwoSpaces,
  }
) =>
  withDir(async (tempDir) => {
    const tempFile = await Deno.makeTempFile({ dir: tempDir });
    const project = new Project({
      libFolderPath: tempDir,
      manipulationSettings,
    });
    const source = project.addSourceFileAtPath(tempFile);
    await cb(source);
  });

加えてfile同士を比較するアサーションの定義もしておきます。

assertEqualFiles.ts
import { assertEquals } from "https://deno.land/std@0.132.0/testing/asserts.ts";

export const assertEqualFile = async (
  receivedPath: string,
  expectedPath: string
) => {
  const [received, expected] = await Promise.all([
    Deno.readTextFile(receivedPath),
    Deno.readTextFile(expectedPath),
  ]);
  await assertEquals(received, expected);
};

テストケースの実装

今回はスナップショットテストの出力結果をハードコーディングしていますが、実装の仕方によっては自動生成も可能だと思います。

output.ts.snapshot
function hoge(piyo: string): string;
function hoge(piyo: string, foo: number): string;
function hoge(foo: number, bar: boolean): string;
function hoge(piyo: string, foo: number, bar: number): string;
function hoge() {
  return ""
}

あくまでoverloadの検査であって、関数自体の検査は含まれていないので最低限の実装をテスト前に追加します。

test.ts
import { StructureKind } from "https://deno.land/x/ts_morph@14.0.0/mod.ts";
import { withSource } from "./helper.ts";
import { assertEqualFile } from "./assertEqualFiles.ts";
import { addCustomEntries } from "./mod.ts";

Deno.test(
  "addCustomEntries",
  { permissions: { read: true, write: true } },
  () =>
    withSource(async (source) => {
      const constructor = source.addFunction({
        name: "hoge",
        kind: StructureKind.Function,
        statements: 'return ""',
      });
      addCustomEntries(
        [
          {
            parameters: [{ name: "piyo", type: "string" }],
          },
          {
            parameters: [
              { name: "piyo", type: "string" },
              { name: "foo", type: "number" },
            ],
          },
          {
            parameters: [
              { name: "foo", type: "number" },
              { name: "bar", type: "number" },
            ],
          },
          {
            parameters: [
              { name: "piyo", type: "string" },
              { name: "foo", type: "number" },
              { name: "bar", type: "number" },
            ],
          },
        ],
        constructor
      );
      await source.save();
      assertEqualFile(source.getFilePath(), "output.ts.snapshot");
    })
);

実行結果

追記の余談

deno test実行時にフラグを渡すことができるのでアップデート用のフラグを追加することでスナップショットの結果を自動で更新できる様にしてみます。

import {
  assertEquals,
  equal,
} from "https://deno.land/std@0.132.0/testing/asserts.ts";
import { copy } from "https://deno.land/std@0.132.0/fs/mod.ts";

export const assertSnapshot = async (
  receivedPath: string,
  expectedPath: string
) => {
  const [received, expected] = await Promise.all([
    Deno.readTextFile(receivedPath),
    Deno.readTextFile(expectedPath),
  ]);
  if (Deno.args.includes("update_snapshot")) {
    if (equal(received, expected)) return;
    await copy(receivedPath, expectedPath, { overwrite: true });
  } else {
    assertEquals(received, expected);
  }
};

これで以下の様にスナップショットを更新するためのフラグを追加することができました🎉
deno test test.ts --allow-read --allow-write -- update_snapshot

追記の余談の余談

std libraryにスナップショットテストが追加されました🎉

https://deno.land/std@0.136.0/testing#snapshot-testing

これにより自作でスナップショットテストの仕組みを用意せずにスナップショットテストを行うことができます。
ただ、ファイルに対するスナップショットテストというよりかはオブジェクトに対するスナップショットテストであり、改行コードを考慮した比較が行われないため以下の様な差分がわかりにくい結果が出力されてしまいます。

import { assertSnapshot } from "https://deno.land/std@0.136.0/testing/snapshot.ts";

Deno.test("test something", (ctx) =>
    withSource(async (source) => {
      ...
      await source.save();
      const target = await Deno.readTextFile(source.getFilePath());
      await assertSnapshot(ctx, target);
    })
);

かといって自作するのも面倒なので次回のアップデートに期待です。

Discussion