vitestでテスト対象がimportしてるモジュールをモックする方法（typescript編）

# ✅ はじめに
vitestでテスト対象がimportしてるモジュールをモックする方法について、サラッと知りたい人向けの記事です。

https://vitest.dev/guide/mocking.html

---

# ✅ サンプルコード
![typescript-vitest-mock-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141934/03fa3e8c-6425-4773-b25d-e9d2062562e0.png)

```typescript
// foo/index.ts

// barをimport
import { bar } from '../bar'

// bar()を実行して、文字列「foo-->」の後ろに付けて返す
export const foo = (): string => {
    return 'foo-->' + bar()
}
```

```typescript
// bar/index.ts

// 文字列「bar」を返す
export const bar = (): string => {
    return 'bar'
}
```

---

# ✅ モックしない場合
![typescript-vitest-mock-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141934/ddc3f88c-69b5-443c-b751-15061cf33f5f.png)

```typescript
// foo/index.test.ts

// テスト用の関数をimport
import { describe, expect, test } from 'vitest'

// テスト対象モジュールをimport
import { foo } from '.'

// テスト
describe('テスト', () => {

  test('foo', () => {
    // foo()を実行する
    const result = foo()
    
    // 結果が「foo-->bar」であること
    expect(result).toBe('foo-->bar')
  })

})
```

# ✅ vi.spyOnでモックする場合
![typescript-vitest-mock-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141934/6bea064d-61fe-48fc-8343-7cb57801897a.png)

```typescript
// foo/index.test.ts


// テスト用の関数をimport
import { describe, expect, test, vi } from 'vitest'

// テスト対象モジュール
import { foo } from '.'

// モック対象モジュール
import * as barModule from '../bar'

// テスト
describe('同期関数', () => {

  test('foo', () => {
    // bar()をモックする
    const barSpy = vi.spyOn(barModule, 'bar').mockReturnValue('mock');

    // foo()を実行する
    const result = foo();

    // 結果が「foo-->mock」であること
    expect(result).toBe('foo-->mock');
    expect(barSpy).toHaveBeenCalledWith(1);
    expect(barSpy).toHaveBeenCalledTimes(1);

    // モックをリセット
    barSpy.mockRestore();
  })

})
```

---

# ✅ vi.mockでモックする場合
![typescript-vitest-mock-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141934/ab11d07b-18ce-49f4-994d-f10d22b1ef0a.png)

## 📝 パターン１：戻り値が固定で良い場合

```typescript
// foo/index.test.ts

// テスト用の関数をimport
import { describe, expect, test, vi } from 'vitest'

// テスト対象モジュールをimport
import { foo } from '.'

// barをモックして、「mock」を返すようにする
vi.mock('../bar', () => ({
  bar: vi.fn(() => 'mock')
}))

// テスト
describe('テスト', () => {

  test('foo', () => {
    // foo()を実行する
    const result = foo()
    
    // 結果が「foo-->mock」であること
    expect(result).toBe('foo-->mock')
  })

})
```

## 📝 パターン２：戻り値をテスト内で設定する場合

```typescript
// foo/index.test.ts

// テスト用の関数をimport
import { describe, expect, test, vi } from 'vitest'

// テスト対象モジュールをimport
import { foo } from '.'

// モック対象モジュールをimport
import { bar } from '../bar'

// barをモックして、Mock型で定義しておく
// (戻り値は設定しない)
vi.mock('../bar');
const barMock = bar as Mock;

// テスト
describe('テスト', () => {

  test('foo', () => {
    // モックに戻り値を設定する
    barMock.mockReturnValue('mock')
    
    // foo()を実行する
    const result = foo()
    
    // 結果が「foo-->mock」であること
    expect(result).toBe('foo-->mock')
  })

})
```

## 📝 モック関数

### モック対象が同期関数の場合

```typescript
// モック対象が同期関数の場合
xxxMock.mockReturnValue('mock')
//=> モックは'mock'を返す
```

### モック対象が非同期関数の場合

```typescript
// モック対象が非同期関数の場合
xxxMock.mockResolvedValue('mock')
//=> モックは'mock'を返す
```

### モックに引数を渡したい場合

```typescript
// モックに引数を渡したい場合
xxxMock.mockImplementation((i) => 'mock'.repeat(i))
// モックに引数「1」が渡された場合、モックは'mock'を返す
// モックに引数「2」が渡された場合、モックは'mockmock'を返す
```