Jasmine × Karmaで始めるAngular単体テスト入門

## **はじめに**
Angular開発において、単体テストはアプリケーションの品質を確保する重要なプロセスです。AngularではデフォルトでJasmineとKarmaが設定されており、これらを使うことで簡単にテスト環境を構築できます。この記事では、JasmineとKarmaの基本を理解し、コンポーネントやサービスの単体テストを実装する方法を解説します。

---

## **目次**
1. JasmineとKarmaとは？
2. Angularのテスト環境のセットアップ
3. 基本的なJasmineの構文
4. コンポーネントの単体テスト
5. サービスの単体テスト
6. 実践：簡単なテストケースの作成
7. トラブルシューティングとベストプラクティス

---

## **1. JasmineとKarmaとは？**
- **Jasmine**  
  - JavaScriptのテストフレームワーク。BDD（振る舞い駆動開発）スタイルで記述できる。
  - シンプルな構文で、テストケースの記述が容易。
- **Karma**  
  - テストランナー。ブラウザでテストを実行し、結果をリアルタイムで確認できる。
  - Angular CLIに組み込まれており、セットアップ不要で利用可能。

---

## **2. Angularのテスト環境のセットアップ**
Angular CLIで生成されたプロジェクトには、すでにJasmineとKarmaが設定されています。

### **テストの実行方法**
```bash
ng test
```
これにより、Karmaが起動し、テストがブラウザ上で実行されます。

### **テストファイル**
- ファイル名: `*.spec.ts`  
- 各コンポーネントやサービスのテストファイルが自動生成されます。

---

## **3. 基本的なJasmineの構文**
### **テストケースの基本構造**
```typescript
describe('テストスイート名', () => {
  it('テストケース名', () => {
    expect(true).toBe(true);
  });
});
```

### **マッチャの例**
- `toBe`：厳密な等価性を確認
- `toEqual`：オブジェクトの値を比較
- `toContain`：配列や文字列に特定の要素が含まれるかを確認
```typescript
expect(1 + 1).toBe(2);
expect({ name: 'Angular' }).toEqual({ name: 'Angular' });
expect([1, 2, 3]).toContain(2);
```

---

## **4. コンポーネントの単体テスト**
コンポーネントのテストでは、テンプレートのレンダリングやユーザーイベントの処理を確認します。

### **テスト対象コンポーネント**
```typescript
@Component({
  selector: 'app-my-component',
  template: `<button (click)="increment()">Click me</button><p>{{ count }}</p>`
})
export class MyComponent {
  count = 0;

  increment() {
    this.count++;
  }
}
```

### **テストケース**
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [MyComponent]
    }).compileComponents();

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

  it('should increment count on button click', () => {
    const button = fixture.nativeElement.querySelector('button');
    button.click();
    fixture.detectChanges();
    expect(component.count).toBe(1);
  });
});
```

---

## **5. サービスの単体テスト**
サービスはビジネスロジックやデータ取得を扱う重要な部分です。

### **テスト対象サービス**
```typescript
@Injectable({
  providedIn: 'root',
})
export class MyService {
  getMessage(): string {
    return 'Hello, Service!';
  }
}
```

### **テストケース**
```typescript
import { TestBed } from '@angular/core/testing';
import { MyService } from './my-service.service';

describe('MyService', () => {
  let service: MyService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(MyService);
  });

  it('should return correct message', () => {
    expect(service.getMessage()).toBe('Hello, Service!');
  });
});
```

---

## **6. 実践：簡単なテストケースの作成**
以下の例では、ToDoアプリのテストを実装します。

### **テスト対象**
```typescript
@Component({
  selector: 'app-todo',
  template: `<input [(ngModel)]="newTask"><button (click)="addTask()">Add</button><ul><li *ngFor="let task of tasks">{{ task }}</li></ul>`
})
export class TodoComponent {
  tasks: string[] = [];
  newTask = '';

  addTask() {
    if (this.newTask.trim()) {
      this.tasks.push(this.newTask);
      this.newTask = '';
    }
  }
}
```

### **テストケース**
```typescript
it('should add a task to the list', () => {
  component.newTask = 'Test Task';
  component.addTask();
  expect(component.tasks).toContain('Test Task');
});
```

---

## **7. トラブルシューティングとベストプラクティス**
- **テストが失敗する場合の対処法**
  - `TestBed`の設定を見直す。
  - 非同期処理の場合、`async`や`fakeAsync`を活用する。
- **ベストプラクティス**
  - テストを小さく、独立したものにする。
  - 可能な限りモックを使用して依存関係をテスト対象から分離する。

---

## **おわりに**
JasmineとKarmaを使えば、Angularアプリケーションの単体テストを効率的に実施できます。この記事で紹介した方法を参考に、ぜひプロジェクトにテストを導入してみてください。

---