Jasmine × Karma実践編：モックの活用とカバレッジの完全ガイド

## **はじめに**
前回の記事では、JasmineとKarmaを用いた基本的な単体テストの方法を学びました。本記事では、より実践的な内容として、モックを使った依存関係の分離方法と、テストカバレッジの取得・向上方法を解説します。モックを活用することで、外部依存を排除し、テストを効率的に行えるようになります。

---

## **目次**
1. モックとは？  
2. モックを活用した単体テストの実践例  
3. 非同期処理のテストとモック  
4. カバレッジの取得方法  
5. カバレッジを向上させるヒント  
6. おすすめのツールとリソース  

---

## **1. モックとは？**
モック（Mock）は、テスト対象が依存するオブジェクトやサービスの代わりに使用する**偽の実装**です。これにより、依存関係に影響されず、テスト対象の動作を独立して検証できます。

- **なぜモックを使うのか？**  
  - 外部依存（APIコール、データベースアクセス）を排除できる。  
  - 特定の状況を簡単にシミュレートできる。  
  - テストが速く、再現性が高くなる。

---

## **2. モックを活用した単体テストの実践例**

### **対象サービス**
`MyService`がAPIからデータを取得する簡単なサービスを想定します。

```typescript
@Injectable({
  providedIn: 'root',
})
export class MyService {
  fetchData(): Observable<string[]> {
    return of(['Item 1', 'Item 2', 'Item 3']);
  }
}
```

### **モックを利用したテストケース**
以下のコードは、`MyService`の依存関係をモックし、コンポーネントのテストを行う例です。

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';
import { MyService } from './my-service.service';
import { of } from 'rxjs';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let mockService: jasmine.SpyObj<MyService>;

  beforeEach(() => {
    // モックサービスの作成
    mockService = jasmine.createSpyObj('MyService', ['fetchData']);
    mockService.fetchData.and.returnValue(of(['Mock Item 1', 'Mock Item 2']));

    TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        { provide: MyService, useValue: mockService }, // モックを提供
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should fetch mock data on initialization', () => {
    expect(component.items).toEqual(['Mock Item 1', 'Mock Item 2']);
    expect(mockService.fetchData).toHaveBeenCalled();
  });
});
```

### **ポイント解説**
- **`jasmine.createSpyObj`**: メソッドを持つモックオブジェクトを生成。  
- **`and.returnValue`**: モックメソッドの戻り値を指定。  
- **`providers`**: Angularの依存性注入を使い、モックをテスト対象に注入。  

---

## **3. 非同期処理のテストとモック**

### **対象サービス**
API呼び出しを含む非同期処理の例です。
```typescript
fetchData(): Observable<string[]> {
  return new Observable((observer) => {
    setTimeout(() => {
      observer.next(['Async Item 1', 'Async Item 2']);
      observer.complete();
    }, 1000);
  });
}
```

### **非同期処理のテストケース**
非同期処理は`fakeAsync`または`async`を使用してテストします。

```typescript
it('should handle async data correctly', fakeAsync(() => {
  mockService.fetchData.and.returnValue(
    of(['Async Mock Item 1', 'Async Mock Item 2'])
  );

  component.ngOnInit();
  tick(1000); // 仮想時間を進める
  expect(component.items).toEqual(['Async Mock Item 1', 'Async Mock Item 2']);
}));
```

---

## **4. カバレッジの取得方法**

### **カバレッジレポートの生成**
以下のコマンドを実行します：
```bash
ng test --code-coverage
```

### **カバレッジレポートの保存場所

テスト実行後、coverageディレクトリにレポートが出力されます。

```bash
coverage/
  └── my-angular-app/
      ├── index.html
      ├── ...
```


### **カバレッジレポートの確認**
- **レポートの内容**:  
  - **Statements**: 実行されたコード行の割合。  
  - **Branches**: 条件分岐の網羅率。  
  - **Functions**: 呼び出された関数の割合。  

ブラウザで`index.html`を開き、視覚的に確認できます。

---

## **5. カバレッジを向上させるヒント**

### **未カバー部分の特定**
カバレッジレポートで「赤く表示」される部分が、未カバーのコードです。これに基づいてテストケースを追加しましょう。

### **よくある見落としポイント**
- 条件分岐（`if`, `switch`）の網羅  
- 例外処理やエラーハンドリングの確認  
- 初期化ロジック（`ngOnInit`など）のテスト  

---

---

## **おわりに**
モックを活用することで、外部依存を切り離し、より効率的かつ信頼性の高い単体テストを実現できます。また、カバレッジレポートを通じてテストの網羅性を評価し、さらなる品質向上に取り組むことが可能です。今回学んだ内容をもとに、あなたのプロジェクトでもテスト環境を強化してみましょう！

---