Vitest の mockClear / mockReset / mockRestore の違いについて

2024/12/18に公開

はじめに

こんにちは!レバウェル開発部アドベントカレンダー18日目です。

普段、スカウトチームではテストランナーに Vitest を使っています。今回は、Vitestのモックをクリアする方法について紹介します!

Vitest のモッククリア

公式ドキュメントを読むと、モックをクリアする方法は一見下記の 3 通りあるように見えます。

しかし、一度読んだだけでは、それぞれのメソッドをどの用途で呼び出せばいいのかわからなかったので調べてみました。

この記事で扱うサンプルプログラム

説明に入る前にこの記事で扱うサンプルプログラムについて説明します。
有効ユーザー数を取得して、整列して返却するプログラムを想定しています。ここでいう有効ユーザーとは age フィールドが undefined でないユーザーのことを指します。また、並び替えは年齢の低い順で行います。

getValidUsers.ts
export type User = {
  name: string;
  age: number | undefined;
}[];

export const getValidUsers = () => {
  // as User | null 型を返す予定
  const users = getUsers();

  if (!users) {
    return [];
  }

  const validUser = users
    .filter((user) => user.age !== undefined)
    .sort((a, b) => a.age! - b.age!);

  return validUser;
};

しかし、上記プログラムで参照しているgetUsersは未実装関数でテストではこのgetUsersをモックすることにします。

getUsers.ts
export const getUsers = () => {
  return "未実装です";
};

モッククリアするメソッドの違い

mockClear

公式ドキュメントによると下記のように記載があります。
()内は翻訳した文章です。

Clears all information about every call. After calling it, all properties on .mock will return to their initial state. This method does not reset implementations. It is useful for cleaning up mocks between different assertions.

(すべての呼び出しに関するすべての情報をクリアする。 このメソッドを呼び出すと、.mock のすべてのプロパティは初期状態に戻ります。 このメソッドは実装をリセットしません。 異なるアサーション間でモックを整理する際に便利です。)

次のようなテストファイルがあったとき、mockClearを呼び出さなくてもmockGetUsersmockReturnValueの値は各テストケース間で上書きされるので、問題なくテストは成功します。

getValidUsers.test.ts
import { describe, expect, test, vi } from "vitest";
import { getValidUsers } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn(),
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    mockGetUsers.mockReturnValue([
      { name: "太郎", age: 32 },
      { name: "一郎", age: 23 },
      { name: "五郎", age: 21 },
    ]);
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);
  });
});

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  16:21:14
   Duration  238ms (transform 34ms, setup 0ms, collect 29ms, tests 3ms, environment 0ms, prepare 59ms)

では次のように、各テストケースでmockGetUsersが 1 回だけ呼び出されたことを確認したい場合はどうでしょうか?

getValidUsers.test.ts
import { describe, expect, test, vi } from "vitest";
import { getValidUsers } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn(),
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
    expect(mockGetUsers).toHaveBeenCalledTimes(1); // 一度だけ呼び出されることを確認したい
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    mockGetUsers.mockReturnValue([
      { name: "太郎", age: 32 },
      { name: "一郎", age: 23 },
      { name: "五郎", age: 21 },
    ]);
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);

    expect(mockGetUsers).toHaveBeenCalledTimes(1); // 一度だけ呼び出されることを確認したい
  });
});

 FAIL  getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
AssertionError: expected "spy" to be called 1 times, but got 2 times
 ❯ getValidUsers.test.ts:38:26
     36|     ]);
     37|
     38|     expect(mockGetUsers).toHaveBeenCalledTimes(1);
       |                          ^
     39|   });
     40| });

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)
   Start at  16:30:29
   Duration  254ms (transform 34ms, setup 0ms, collect 26ms, tests 5ms, environment 0ms, prepare 55ms)

この場合、テストは失敗します。モックはテストケース毎に独立しているのではなく、テストケース毎にモック情報を共有しています。mockClearのドキュメントに、「異なるアサーション間でモックを整理する際に便利です。」と書かれていたのはこういうことだったのですね。

それではmockClearを記載してテストを試みます。

getValidUsers.test.ts
import { describe, expect, test, vi } from "vitest";
import { getValidUsers } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn(),
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
    expect(mockGetUsers).toHaveBeenCalledTimes(1);

    mockGetUsers.mockClear(); // ここを追記
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    mockGetUsers.mockReturnValue([
      { name: "太郎", age: 32 },
      { name: "一郎", age: 23 },
      { name: "五郎", age: 21 },
    ]);
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);

    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
});

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  16:40:18
   Duration  246ms (transform 36ms, setup 0ms, collect 34ms, tests 3ms, environment 0ms, prepare 49ms)

無事テストが通りました。

mockReset

Performs the same actions as mockClear and sets the inner implementation to an empty function (returning undefined when invoked). This also resets all "once" implementations. It is useful for completely resetting a mock to its default state.

(mockClear と同じアクションを実行し、内側の実装を空の関数(呼び出されたときに undefined を返す)に設定します。 これはすべての「once」実装もリセットします。 モックをデフォルトの状態に完全にリセットするのに便利です。)

どうやらmockResetmockClearと同じようなことをしているようです。しかし、mockClearと異なるのは実装もリセットするということのようです。これはどういうことでしょうか?

ここで一度mockClearに話を戻します。先ほどのテストでは各テストケースでmockReturnValueを呼び出して期待する挙動を上書きしていたので、テストが成功していましたが、もし、2 つ目のテストケース側でmockReturnValueを呼び出さなかった場合はどうなるのか確認してみましょう。

getValidUsers.test.ts
import { beforeEach, describe, expect, test, vi } from "vitest";
import { getValidUsers } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn(),
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

// mockClearを先頭に持ってきました
beforeEach(() => {
  mockGetUsers.mockClear();
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    // ここをコメントアウト
    // mockGetUsers.mockReturnValue([
    //   { name: "太郎", age: 32 },
    //   { name: "一郎", age: 23 },
    //   { name: "五郎", age: 21 },
    // ]);
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);

    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
});

 FAIL  getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
AssertionError: expected [] to deeply equal [ { name: '五郎', age: 21 }, …(2) ]

- Expected
+ Received

- Array [
-   Object {
-     "age": 21,
-     "name": "五郎",
-   },
-   Object {
-     "age": 23,
-     "name": "一郎",
-   },
-   Object {
-     "age": 32,
-     "name": "太郎",
-   },
- ]
+ Array []

 ❯ getValidUsers.test.ts:31:19
     29|     const users = getValidUsers();
     30|
     31|     expect(users).toEqual([
       |                   ^
     32|       { name: "五郎", age: 21 },
     33|       { name: "一郎", age: 23 },

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)
   Start at  16:57:47
   Duration  244ms (transform 28ms, setup 0ms, collect 25ms, tests 6ms, environment 0ms, prepare 43ms)

期待する挙動で上書きしていないので当然の結果ではあるのですが、2 つ目のテストケースが落ちてしまいました。それでは、console.log()を仕込んでmockGetUsersの戻り値がどうなっているか確認してみます。

src/getUsers.ts
export type User = {
  name: string;
  age: number | undefined;
}[];

export const getValidUsers = () => {
  // as User | null 型を返す予定
  const users = getUsers();
  console.log(users); // ここにログを仕込む
  if (!users) {
    return [];
  }

  const validUser = users
    .filter((user) => user.age !== undefined)
    .sort((a, b) => a.age! - b.age!);

  return validUser;
};

すると下記の結果が得られます。

stdout | getValidUsers.test.ts > getValidUsers > usersがundefinedの場合、空配列を返すこと
null

stdout | getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
null

するとどちらのテストケースのログでもnullの結果が得られました。これは 1 つ目のテストケースでmockReturnValueの引数に指定した値です。このことから、mockClearでは、呼び出し回数などの情報を各テストケース間でリセットするが、実装はリセットされないことがわかります。(最初からドキュメントには書いてあるんですが、実際に体験してみないと理解しづらかったです。)

長く寄り道しましたが、それではここで、この節の本題のmockResetに戻ります。
mockClearmockResetに置き換えてログを確認してみましょう。

getValidUsers.test.ts
import { beforeEach, describe, expect, test, vi } from "vitest";
import { getValidUsers } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn(),
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

beforeEach(() => {
  mockGetUsers.mockReset(); // ここを変更
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);

    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
});

2 つ目のテストケースでmockGetUsersの値を変更していないのでテストは落ちたままですが、ログに変化がありました。

stdout | getValidUsers.test.ts > getValidUsers > usersがundefinedの場合、空配列を返すこと
null

stdout | getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
undefined

先ほどはnullになっていた 2 つ目のテストケースのログが、undefinedに変わっています。このundefinedはどこからきたのでしょうか?せっかくなので Vitest の内部実装に潜ってみます。
mockResetの実装をみてみると、mockClearを呼び出した後に、implementation = undefinedを実行しているのが確認できます。
undefinedはここからきていたんですね!また、ドキュメントにあった、「mockClear と同じアクションを実行し」という部分も理解ができました。

mockRestore

Performs the same actions as mockReset and restores the inner implementation to the original function.

Note that restoring a mock created with vi.fn() will set the implementation to an empty function that returns undefined. Restoring a mock created with vi.fn(impl) will restore the implementation to impl.

(mockReset と同じアクションを実行し、内側の実装を元の関数に復元します。 vi.fn()で作成したモックを復元すると、実装が undefined を返す空の関数に設定されることに注意してください。 vi.fn(impl)で作成されたモックをリストアすると、実装が impl にリストアされます。)

ドキュメントに記載されている通り、vi.fn()で作成したモックだと、undefinedを返却する関数になってしまい、それだとmockResetとの違いがわかりにくいので、コメント部分を少し変更します。

getValidUsers.test.ts
import { beforeEach, describe, expect, test, vi } from "vitest";
import { getValidUsers, User } from "./getValidUsers";

const { mockGetUsers } = vi.hoisted(() => {
  return {
    mockGetUsers: vi.fn((): User | null => [{ name: "太郎", age: 32 }]), // ここの記述を変更
  };
});

vi.mock("./getUsers", () => {
  return {
    getUsers: mockGetUsers,
  };
});

beforeEach(() => {
  mockGetUsers.mockRestore(); // ここでmockRestore
});

describe("getValidUsers", () => {
  test("usersがundefinedの場合、空配列を返すこと", () => {
    mockGetUsers.mockReturnValue(null);
    const users = getValidUsers();

    expect(users).toEqual([]);
    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
  test("取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること", () => {
    const users = getValidUsers();

    expect(users).toEqual([
      { name: "五郎", age: 21 },
      { name: "一郎", age: 23 },
      { name: "太郎", age: 32 },
    ]);

    expect(mockGetUsers).toHaveBeenCalledTimes(1);
  });
});

このテストを実行してみると次の結果とログが得られます。

// 実行結果
 FAIL  getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
AssertionError: expected [ { name: '太郎', age: 32 } ] to deeply equal [ { name: '五郎', age: 21 }, …(2) ]

- Expected
+ Received

  Array [
    Object {
-     "age": 21,
-     "name": "五郎",
-   },
-   Object {
-     "age": 23,
-     "name": "一郎",
-   },
-   Object {
      "age": 32,
      "name": "太郎",
    },
  ]

 ❯ getValidUsers.test.ts:33:19
     31|     const users = getValidUsers();
     32|
     33|     expect(users).toEqual([
       |                   ^
     34|       { name: "五郎", age: 21 },
     35|       { name: "一郎", age: 23 },

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)
   Start at  20:25:23
   Duration  505ms (transform 87ms, setup 0ms, collect 70ms, tests 15ms, environment 0ms, prepare 137ms)
stdout | getValidUsers.test.ts > getValidUsers > usersがundefinedの場合、空配列を返すこと
null

stdout | getValidUsers.test.ts > getValidUsers > 取得したユーザーリストのうち、ageが登録されているユーザーに絞り、ageの昇順で返却すること
[ { name: '太郎', age: 32 } ]

Received とログをみるとわかるとおり、mockRestoreを実行した 2 回目のテストケースのgetUsersの値が、[ { name: '太郎', age: 32 } ]になっています。これはファイルの上部で設定した値と同一です。mockResetを実行した場合は、2 つ目のテストケースのgetUsersの戻り値がundefinedになっていたのに対して、mockRestoreを実行した場合は元(vi.fn(impl)で作成されたモック)の実装に戻りました。

まとめ

下記の Mock Functions について深掘りしました。

  • mockClear
  • mockReset
  • mockRestore

それぞれ特徴をまとめると下記の通りです。

  • mockClear
    • テストケース間のモック情報をリセットする。しかし、実装はリセットせずそのまま。
  • mockReset
    • テストケース間のモック情報をリセットし、実装もリセットする(undefinedになる)。
  • mockRestore
    • テストケース間のモック情報をリセットし、元の実装に戻す。

こうやって見返してみると、当たり前ですが、公式ドキュメントに書いてある通りですね笑。しかし、執筆開始時点と現在では、明らかにドキュメントの理解度に差があります。簡潔に記載してあって、いまいち理解できていなかったものが、今では具体的に内容を把握しています。エンジニアには、「大事なことはそこに書いてあるから公式ドキュメントを読むべし」という原則のようなものがあると思いますが、書いてあったとしても理解できるとは限らないので、今回のように「わからなかったらわかるまで試すべし」まで実践できると良いなぁと思いました。

Discussion