VitestでReactコンポーネントをテストする際のMock、Spy方法あれこれ

# もくじ
1. [**基本的なmock、spyの手順**](#基本的なmockspyの手順)
1. [**exportされた関数のmock、spy**](#exportされた関数のmockspy)
1. [**global関数のmock、spy**](#global関数のmockspy)
1. [**ライブラリのmock、spy**](#ライブラリのmockspy)
1. [**ReactHooksのmock、spy**](#reacthooksのmockspy)

コードはぜんぶリポジトリにあります。

https://github.com/suiwave/vitest-mocking

## 検証用プロダクトコード
**useCounter.ts**
numberのstateを加算・減算する単純なhooksです。

```src/hooks/useCounter.ts
import { useState } from "react"

export const useCounter = () => {
    const [count, setCount] = useState(0)

    const increase = () => setCount((current) => current + 1)
    const decrease = () => setCount((current) => current - 1)

    return { count, increase, decrease }
}
```

**StringUtil.ts**
stringを整形する単純な関数群です。

```src/functions/StringUtil.ts
export const makeTitle = (title: string) => {
    // title => Title
    return title.charAt(0).toUpperCase() + title.slice(1).toLowerCase() + '!!'
}

export const makeUserName = (userName: string) => {
    // yuji => Yuji san
    return userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() + ' san'
}
```

**App.tsx**
上記hooksやUtil関数を実行してレンダリングされるコンポーネントです。

```src/App.tsx
import { useState } from 'react'
import './App.css'
import axios from 'axios'
import { makeTitle, makeUserName } from './functions/StringUtil'
import { useCounter } from './hooks/useCounter'

function App() {
  const [result, setResult] = useState("init")
  const onClickFetch = () => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => setResult(JSON.stringify(json)))
  }

  const onClickAxios = () => {
    axios.get('https://jsonplaceholder.typicode.com/todos/2')
      .then(json => setResult(JSON.stringify(json.data)))
  }

  const { count, increase, decrease } = useCounter()

  return (
    <>
      <h1>{makeTitle('vite + React')}</h1>
      <p>{makeUserName('yuji')}</p>
      <div>
        <span>{result}</span>
      </div>
      <button onClick={onClickFetch}>call fetch</button>
      <button onClick={onClickAxios}>call axios</button>
      <div>
        <span>count is {count}</span>
      </div>
      <button onClick={increase}>call increase</button>
      <button onClick={decrease}>call decrease</button>
    </>
  )
}

export default App
```
## 基本的なmock、spyの手順
エクスポートされた関数をmock、spyする例を参考に、基本的なmock、spyの手順をまとめます。

<details><summary>サンプルコード全て</summary>

```vitest-mocking/__test__/samples/basicUsage/sample.mock.test.tsx
import { beforeEach, describe, it, expect, vi } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import React from 'react'

import App from '../../../src/App.tsx'
import * as StringUtil from "../../../src/functions/StringUtil.ts";

const MOCKED_TITLE = "mocked!!"
const DEFAULT_USERNAME = "Yuji san"

beforeEach(() => {
    cleanup();
});

// mockはmock宣言 → import(require)の順で評価される必要があるため、どこで宣言しても巻き上げられる
// https://zenn.dev/ptna/articles/617b0884f6af0e#mock%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%B7%BB%E3%81%8D%E4%B8%8A%E3%81%92%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6
vi.mock("../../../src/functions/StringUtil.ts", async () => {
    // 一部関数のみimportする場合の方法
    // mock化はimportより先に実施されるため、import以外の参照方法が必要
    // https://vitest.dev/api/vi#vi-importactual
    const original = await vi.importActual("../../../src/functions/StringUtil.ts")
    return {
        ...original,
        makeTitle: vi.fn(() => MOCKED_TITLE),
    }
})

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('exportされた関数のmockサンプル', () => {
    it("exportされた単純な関数をmockする", () => {
        // 準備
        // 検証
        render(<App />)

        // 検証
        // mockされた値が関数から返却されるはず
        expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)
    })
    it("任意の関数のみmock化する", () => {
        // 準備
        // 検証
        render(<App />)

        // 検証
        // USERNAMEはStringUtil.ts内に存在するmakeUserName関数を使用している。mockされていないことを確認する
        expect(screen.getByRole("paragraph").textContent).toBe(DEFAULT_USERNAME)
    })
    it("mockした関数をspyする", () => {
        // 準備
        const spy = vi.spyOn(StringUtil, "makeTitle");

        // 検証
        render(<App />)

        // 検証
        // 対象の関数が実行されたことを検証
        expect(spy).toHaveBeenCalled()
        expect(spy).toHaveBeenCalledOnce()
        expect(spy).toHaveBeenCalledTimes(1)

        // 実行された際の引数の検証
        expect(spy).toHaveBeenCalledWith("vite + React")

        // 実行された際の返却値の検証
        expect(spy).toHaveReturnedWith(MOCKED_TITLE)
    })
})
```

```vitest-mocking/__test__/samples/basicUsage/sample.spy.test.tsx
import { beforeEach, describe, it, expect, vi } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import React from 'react'

import App from '../../../src/App.tsx'
import * as StringUtil from "../../../src/functions/StringUtil.ts";

const DEFAULT_TITLE = "Vite + react!!"
const MOCKED_TITLE = "mocked!!"

beforeEach(() => {
    cleanup();
});

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('exportされた関数のspyサンプル', () => {
    describe('普通のケースの挙動を確認', () => {
        it('titleが存在すること', () => {
            // 準備
            // 実行
            render(<App />)

            // 検証
            expect(screen.getByRole("heading").textContent).toBe(DEFAULT_TITLE)
        })
    })
    describe("exportされた単純な関数をspyする", () => {
        it("spyして関数の利用のされかたを追跡する", () => {
            // 準備
            // spy変数を作成する
            // spyは関数の実装をデフォルトでは上書きしない
            // 関数の呼ばれ方などを追跡するのに便利
            const spy = vi.spyOn(StringUtil, "makeTitle");

            // 実行
            // App.tsxは内部でmakeTitle関数を使用する（はず）
            render(<App />)

            // 検証
            // 関数が壊れていないこと
            expect(screen.getByRole("heading").textContent).toBe(DEFAULT_TITLE)

            // 対象の関数が実行されたことを検証
            expect(spy).toHaveBeenCalled()
            expect(spy).toHaveBeenCalledOnce()
            expect(spy).toHaveBeenCalledTimes(1)

            // 実行された際の引数の検証
            expect(spy).toHaveBeenCalledWith("vite + React")

            // 実行された際の返却値の検証
            expect(spy).toHaveReturnedWith(DEFAULT_TITLE)
        })
        it("spyで一時的に関数の戻り値を変更する", () => {
            // 準備
            const spy = vi.spyOn(StringUtil, "makeTitle");
            spy.mockReturnValueOnce(MOCKED_TITLE)

            // 実行
            // App.tsxは内部でmakeTitle関数を使用する（はず）
            render(<App />)

            // 検証
            expect(spy).toHaveReturnedWith(MOCKED_TITLE)
            expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)

            // mockReturnValueOnceがOnceなことを確認する
            // js-domをcleanupした後、再度コンポーネントをレンダリングする
            cleanup()
            render(<App />)

            expect(screen.getByRole("heading").textContent).toBe(DEFAULT_TITLE)
        })
        describe("spyで恒久的に関数の戻り値を変更する", () => {
            it("spyで恒久的に関数の戻り値を変更する", () => {
                // 準備
                const spy = vi.spyOn(StringUtil, "makeTitle");
                spy.mockReturnValue(MOCKED_TITLE)

                // 実行
                // App.tsxは内部でmakeTitle関数を使用する（はず）
                render(<App />)

                // 検証
                expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)

                // mockReturnValueがeternalなことを確認する
                cleanup()
                render(<App />)

                expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)
            })
            it("戻り値の変更を破棄する", () => {
                // 準備
                const spy = vi.spyOn(StringUtil, "makeTitle");
                spy.mockReturnValue(MOCKED_TITLE)

                // 実行
                // App.tsxは内部でmakeTitle関数を使用する（はず）
                render(<App />)

                // 検証
                expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)

                // vi.restoreAllMocks()でmockReturnValueによる戻り値の変更を破棄する
                // https://vitest.dev/api/vi.html#vi-spyon
                // afterEach hooksで実行するのが良い
                vi.restoreAllMocks()
                cleanup()
                render(<App />)

                expect(screen.getByRole("heading").textContent).toBe(DEFAULT_TITLE)
            })
        })
    })
})
```
</details>

### Mock
関数などをmockする場合は、```vi.mock()``` 関数を使用します。

```sample.mock.test.tsx
// L15
// mockはmock宣言 → import(require)の順で評価される必要があるため、どこで宣言しても巻き上げられる
// https://zenn.dev/ptna/articles/617b0884f6af0e#mock%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%B7%BB%E3%81%8D%E4%B8%8A%E3%81%92%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6
vi.mock("../../../src/functions/StringUtil.ts", async () => {
    // 一部関数のみimportする場合の方法
    // mock化はimportより先に実施されるため、import以外の参照方法が必要
    // https://vitest.dev/api/vi#vi-importactual
    const original = await vi.importActual("../../../src/functions/StringUtil.ts")
    return {
        ...original,
        makeTitle: vi.fn(() => MOCKED_TITLE),
    }
})
```

mock化に成功すると、mock時に指定した戻り値を戻すことができます。

```sample.mock.test.tsx
// L35
    // 検証
    // mockされた値が関数から返却されるはず
    expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)
```

### Spy
関数などをmockする場合は、```vi.spyOn()``` 関数を使用します。

```sample.spy.test.tsx
// L6
import * as StringUtil from "../../../src/functions/StringUtil.ts";

/* 中略 */
// L27
    describe("exportされた単純な関数をspyする", () => {
        it("spyして関数の利用のされかたを追跡する", () => {
            // 準備
            // spy変数を作成する
            // spyは関数の実装をデフォルトでは上書きしない
            // 関数の呼ばれ方などを追跡するのに便利
            const spy = vi.spyOn(StringUtil, "makeTitle");

```

第一引数に関数が定義されているオブジェクトを指定します。
簡単ですね。

spy変数が用意できた後は、実行回数や実行時の引数などが検証できます。

```sample.spy.test.tsx
// L43
    // 対象の関数が実行されたことを検証
    expect(spy).toHaveBeenCalled()
    expect(spy).toHaveBeenCalledOnce()
    expect(spy).toHaveBeenCalledTimes(1)

    // 実行された際の引数の検証
    expect(spy).toHaveBeenCalledWith("vite + React")

    // 実行された際の返却値の検証
    expect(spy).toHaveReturnedWith(DEFAULT_TITLE)
```

## exportされた関数のmock、spy
上記例で記載した通りですが、整理のため再掲。

### exportされた関数のmock
```__test__/samples/exportedFunction/mock.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import React from 'react'

import App from '../../../src/App.tsx'

const MOCKED_TITLE = "mocked!!"

// mockはmock宣言 → import(require)の順で評価される必要があるため、どこで宣言しても巻き上げられる
// https://zenn.dev/ptna/articles/617b0884f6af0e#mock%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%B7%BB%E3%81%8D%E4%B8%8A%E3%81%92%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6
vi.mock("../../../src/functions/StringUtil.ts", async () => {
    // 一部関数のみimportする場合の方法
    // mock化はimportより先に実施されるため、import以外の参照方法が必要
    // https://vitest.dev/api/vi#vi-importactual
    const original = await vi.importActual("../../../src/functions/StringUtil.ts")
    return {
        ...original,
        makeTitle: vi.fn(() => MOCKED_TITLE),
    }
})

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('exportされた関数のmockサンプル', () => {
    it("exportされた単純な関数をmockする", () => {
        // 準備
        // 検証
        render(<App />)

        // 検証
        // mockされた値が関数から返却されるはず
        expect(screen.getByRole("heading").textContent).toBe(MOCKED_TITLE)
    })
})

```

StringUtil.tsには2つの関数がエクスポートされています。originalをimportし、```makeTitle関数``` だけmock関数で上書きしているところがポイントです。
```ts
    return {
        ...original,
        makeTitle: vi.fn(() => MOCKED_TITLE),
    }
```

### exportされた関数のspy
こちらも再掲

```__test__/samples/exportedFunction/spy.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import React from 'react'

import App from '../../../src/App.tsx'
import * as StringUtil from "../../../src/functions/StringUtil.ts";

const DEFAULT_TITLE = "Vite + react!!"

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('exportされた関数のspyサンプル', () => {
    describe("exportされた単純な関数をspyする", () => {
        it("spyして関数の利用のされかたを追跡する", () => {
            // 準備
            // spy変数を作成する
            // spyは関数の実装をデフォルトでは上書きしない
            // 関数の呼ばれ方などを追跡するのに便利
            const spy = vi.spyOn(StringUtil, "makeTitle");

            // 実行
            render(<App />)

            // 検証
            // 関数が壊れていないこと
            expect(screen.getByRole("heading").textContent).toBe(DEFAULT_TITLE)

            // 対象の関数が実行されたことを検証
            expect(spy).toHaveBeenCalled()
            expect(spy).toHaveBeenCalledOnce()
            expect(spy).toHaveBeenCalledTimes(1)

            // 実行された際の引数の検証
            expect(spy).toHaveBeenCalledWith("vite + React")

            // 実行された際の返却値の検証
            expect(spy).toHaveReturnedWith(DEFAULT_TITLE)
        })
    })
})
```


## global関数のmock、spy
### global関数？
fetchなどが該当します。呼び方違ってるかも

### global関数のmock

```__test__/samples/globalExportedFunction/mock.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import '@testing-library/jest-dom/vitest';
import React from 'react'

import App from '../../../src/App.tsx'

const MOCKED_JSON = { message: 'Mocked fetch response' }

// mock関数ではなく、stubGlobal関数を使用する
vi.stubGlobal('fetch', vi.fn(() =>
    Promise.resolve({
        json: () => Promise.resolve(MOCKED_JSON),
    })
));

// userインスタンスのセットアップ
const user = userEvent.setup();

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('global関数のmockサンプル', () => {
    it("global関数をmockする", async () => {
        // 準備
        // 検証
        render(<App />)
        await user.click(screen.getByRole("button", { name: /fetch/ }))

        // 検証
        // mockされた値が関数から返却されるはず
        expect(screen.getByText(JSON.stringify(MOCKED_JSON))).toBeInTheDocument()
    })
})
```

ここですね

```__test__/samples/globalExportedFunction/mock.test.tsx
// mock関数ではなく、stubGlobal関数を使用する
vi.stubGlobal('fetch', vi.fn(() =>
    Promise.resolve({
        json: () => Promise.resolve(MOCKED_JSON),
    })
));
```

vitestには[stubGlobal](https://vitest.dev/api/vi#vi-stubglobal)というGlobalに定義されている変数などを変更できる関数があります。
この関数を使用し、メソッドをまるまるmock化できます。

### global関数のspy

```__test__/samples/globalExportedFunction/sample.spy.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import '@testing-library/jest-dom/vitest';
import React from 'react'

import App from '../../../src/App.tsx'

const DEFAULT_JSON = { userId: 1, "id": 1, title: "delectus aut autem", completed: false }

// userインスタンスのセットアップ
const user = userEvent.setup();

describe('global関数関数のspyサンプル', () => {
    describe("global関数関数をspyする", () => {
        it("spyして関数の利用のされかたを追跡する", async () => {
            // 準備
            // spy変数を作成する
            // 第一引数にgrobalを指定することで、グローバル関数をspyできる
            const spy = vi.spyOn(global, "fetch");

            // 実行
            render(<App />)

            await user.click(screen.getByRole("button", { name: /fetch/ }))

            // 検証
            // 関数が壊れていないこと
            // ボタンがクリックされた後、画面表示が切り替わるまでに100msくらいのラグがあるので、waitForする
            await waitFor(() => expect(screen.getByText(JSON.stringify(DEFAULT_JSON))).toBeInTheDocument())

            // 対象の関数が実行されたことを検証
            expect(spy).toHaveBeenCalled()
            expect(spy).toHaveBeenCalledOnce()
            expect(spy).toHaveBeenCalledTimes(1)

            // 実行された際の引数の検証
            expect(spy).toHaveBeenCalledWith("https://jsonplaceholder.typicode.com/todos/1")
        })
    })
})
```

ここです。

```__test__/samples/globalExportedFunction/sample.spy.test.tsx
// spy変数を作成する
// 第一引数にgrobalを指定することで、グローバル関数をspyできる
const spy = vi.spyOn(global, "fetch");
```

エクスポートされた関数などの際は関数をもつオブジェクトを第一引数に指定していました。
Global変数の場合はglobalというなぞの変数を指定します。どこから来た何なのでしょうか。

## ライブラリのmock、spy
Axiosで試してみます。

### ライブラリのmock

```__test__/samples/importedLibrary/mock.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import '@testing-library/jest-dom/vitest';
import React from 'react'
import App from '../../../src/App.tsx'
import axios from 'axios';

const MOCKED_JSON = { message: 'Mocked fetch response' }

vi.mock('axios', async () => {
    const original = await vi.importActual('axios');
    return {
        default: {
            ...original,
            // 巻き上げられるため、他の変数を参照できない。なんかやり方はありそう。いったんそこはスコープ外
            get: vi.fn().mockResolvedValue({ data: { message: 'Mocked fetch response' } })
        }
    };
});


// // userインスタンスのセットアップ
const user = userEvent.setup();

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('importしているライブラリのmockサンプル', () => {
    it("importしているライブラリをmockする", async () => {
        // 準備
        // 実行
        render(<App />)
        await user.click(screen.getByRole("button", { name: /axios/ }))

        // 検証
        // mockされた値が関数から返却されるはず
        await waitFor(() => expect(screen.getByText(JSON.stringify(MOCKED_JSON))).toBeInTheDocument())
    })
    it("importしているライブラリの一部だけmockできる", async () => {
        // 準備
        // 実行

        // 検証
        expect(axios.VERSION).toBe("1.7.9")
    })
})

```

ここですね。
フロントエンドで使用しているAxiosはデフォルトエクスポートされているメンバーなので、axiosのdefaultにmockを仕込みます。

```__test__/samples/importedLibrary/mock.test.tsx
vi.mock('axios', async () => {
    const original = await vi.importActual('axios');
    return {
        default: {
            ...original,
            // 巻き上げられるため、他の変数を参照できない。なんかやり方はありそう。いったんそこはスコープ外
            get: vi.fn().mockResolvedValue({ data: { message: 'Mocked fetch response' } })
        }
    };
});
```

### ライブラリのspy
```__test__/samples/importedLibrary/sample.spy.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import '@testing-library/jest-dom/vitest';
import React from 'react'

import App from '../../../src/App.tsx'
import axios from 'axios';

const DEFAULT_JSON = { userId: 1, "id": 2, title: "quis ut nam facilis et officia qui", completed: false }

// userインスタンスのセットアップ
const user = userEvent.setup();

describe('importしているライブラリのspyサンプル', () => {
    describe("importしているライブラリをspyする", () => {
        it("spyして関数の利用のされかたを追跡する", async () => {
            // 準備
            // spy変数を作成する
            const spy = vi.spyOn(axios, "get");

            // 実行
            render(<App />)

            await user.click(screen.getByRole("button", { name: /axios/ }))

            // 検証
            // 関数が壊れていないこと
            await waitFor(() => expect(screen.getByText(JSON.stringify(DEFAULT_JSON))).toBeInTheDocument())

            // 対象の関数が実行されたことを検証
            expect(spy).toHaveBeenCalled()
            expect(spy).toHaveBeenCalledOnce()
            expect(spy).toHaveBeenCalledTimes(1)

            // 実行された際の引数の検証
            expect(spy).toHaveBeenCalledWith("https://jsonplaceholder.typicode.com/todos/2")
        })
    })
})
```

ここです。めっちゃ簡単

```__test__/samples/importedLibrary/sample.spy.test.tsx
import axios from 'axios';

describe('importしているライブラリのspyサンプル', () => {
    describe("importしているライブラリをspyする", () => {
        it("spyして関数の利用のされかたを追跡する", async () => {
            // 準備
            // spy変数を作成する
            const spy = vi.spyOn(axios, "get");
```


## ReactHooksのmock、spy
Hooksの作りを意識すると理解がスムーズです。
通常、Hooksはインポート後、インポートした関数を実行し、関数内で作成されたstateや更新関数を受け取ります。

### ReactHooksのmock

```__test__/samples/reactHooks/mock.test.tsx
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import '@testing-library/jest-dom/vitest';
import React from 'react'
import App from '../../../src/App.tsx'

beforeEach(() => {
    cleanup();
});

vi.mock('../../../src/hooks/useCounter.ts', async () => {
    // mock作成処理中にactual.useCounter()は実行できない。Reactではなくvitestの環境内のため
    const actual = await vi.importActual<typeof import('../../../src/hooks/useCounter.ts')>('../../../src/hooks/useCounter.ts');

    return {
        useCounter: () => {
            // mockしたuseCounterが実行されると、事前にimportした実際のuseCounterが実行される
            // 実行された実際の結果を使用することで、部分的な書き換えを行うことができる
            const originalHook = actual.useCounter();
            return {
                ...originalHook,
                increase: vi.fn() // mockしたい部分だけ置き換える
            };
        }
    };
});

// // userインスタンスのセットアップ
const user = userEvent.setup();

// mockはファイル全体に影響するので、spyとファイルを分ける
describe('React Hooksのmockサンプル', () => {
    it("React Hooksをmockする", async () => {
        // 準備
        // 実行
        render(<App />)
        await user.click(screen.getByRole("button", { name: /increase/ }))

        // 検証
        await waitFor(() => expect(screen.getByText('count is 0')).toBeInTheDocument())
    })
    it("React Hooksの一部だけmockできる", async () => {
        // 準備
        // 実行
        render(<App />)
        await user.click(screen.getByRole("button", { name: /decrease/ }))

        // 検証
        // decrease関数はmock化していないのでcountが減少する
        await waitFor(() => expect(screen.getByText('count is -1')).toBeInTheDocument())
    })
})
```

ここですね。結構ややこしいです。
```useCounter``` が実行された場合、オブジェクトが返却されます。
全てmockすることも可能ですし、今回実装したように一部のみをmockすることもできます。

```__test__/samples/reactHooks/mock.test.tsx
vi.mock('../../../src/hooks/useCounter.ts', async () => {
    // mock作成処理中にactual.useCounter()は実行できない。Reactではなくvitestの環境内のため
    const actual = await vi.importActual<typeof import('../../../src/hooks/useCounter.ts')>('../../../src/hooks/useCounter.ts');

    return {
        useCounter: () => {
            // mockしたuseCounterが実行されると、事前にimportした実際のuseCounterが実行される
            // 実行された実際の結果を使用することで、部分的な書き換えを行うことができる
            const originalHook = actual.useCounter();
            return {
                ...originalHook,
                increase: vi.fn() // mockしたい部分だけ置き換える
            };
        }
    };
});
```

### ReactHooksのspy
**良い感じにspyできなかったです。素直にmockするのが良いかも**

```__test__/samples/reactHooks/sample.spy.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from "@testing-library/user-event";
import React from 'react'

import App from '../../../src/App.tsx'
import * as CounterHooks from '../../../src/hooks/useCounter.ts';

// userインスタンスのセットアップ
const user = userEvent.setup();

describe('React Hooksのspyサンプル', () => {
    describe("React Hooksをspyする", () => {
        it("spyして関数の利用のされかたを追跡する", async () => {
            // 準備
            // spy変数を作成する
            // hooksはうまいことspyできなかった・・・。mock化するか、useCounter関数自体をspyするしかなさそう？
            const useCounterSpy = vi.spyOn(CounterHooks, "useCounter");
            const decreaseSpy = vi.fn();

            // spyを埋め込む
            useCounterSpy.mockReturnValue({
                count: 0,
                increase: vi.fn(),
                decrease: decreaseSpy, // spy化したdecrease関数
            });

            // 実行
            render(<App />)

            await user.click(screen.getByRole("button", { name: /decrease/ }))

            // 検証
            // 対象の関数が実行されたことを検証
            expect(decreaseSpy).toHaveBeenCalled()
            expect(decreaseSpy).toHaveBeenCalledOnce()
            expect(decreaseSpy).toHaveBeenCalledTimes(1)
        })
    })
})
```

ここです。上記した通り、若干微妙ですね
他にもいろいろやり方があるのと、mockでほぼspyっぽいことができるので、今回は深追いしませんでした。

```__test__/samples/reactHooks/sample.spy.test.tsx
            // spy変数を作成する
            // hooksはうまいことspyできなかった・・・。mock化するか、useCounter関数自体をspyするしかなさそう？
            const useCounterSpy = vi.spyOn(CounterHooks, "useCounter");
            const decreaseSpy = vi.fn();

            // spyを埋め込む
            useCounterSpy.mockReturnValue({
                count: 0,
                increase: vi.fn(),
                decrease: decreaseSpy, // spy化したdecrease関数
            });
```

## 知らなかったおまけ

![cap.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/2f7b65a8-6e04-1901-b8af-7705a2c04668.png)

>Since Vitest 2.1, you can also provide an object with a spy property instead of a factory function. If spy is true, then Vitest will automock the module as usual, but it won't override the implementation of exports. This is useful if you just want to assert that the exported method was called correctly by another method.

らしいです

https://vitest.dev/api/vi#vi-mock

## ブランチ再掲

https://github.com/suiwave/vitest-mocking