NgRx MockStoreについて

この記事は [Angular Advent Calendar 2018](https://qiita.com/advent-calendar/2018/angular) の 18日目の記事です。
今回は最近NgRxの新機能として追加されたMockStoreについて書きたいと思います。

NgRxの基本的な使い方については14日目で[kiita312](https://qiita.com/kiita312)さんが書かれていますので、ぜひそちらをご参考ください。
[Ngrxをこれから始める人への入門ガイド](https://qiita.com/kiita312/items/8255c173c30831122648)


## MockStoreとは
MockStoreとは、Storeを使ったComponentでのテストを簡単に行えるようにする為のモジュールです。  
10月末にmasterにマージされ、v7.0.0でリリース予定となりました。

まだドキュメントが作成されておらず[ngrx.io](https://ngrx.io/)にも記載がないのですが、機能自体はstableとなっています。
非常に便利なので、v7(beta)を使用している方はこの記事を参考にぜひ使い始めてみてください。

残念ながらv7は現在(2018-12-18時点)まだstableとしてリリースされていないため、6系を使用している方はもう少しだけ待つ必要があります。


## MockStoreの目的
MockStoreはStoreを使用しているComponentのテストをシンプルに書けるようにするために作られています。

NgRxではComponentはStoreに対してselect/dispatchを行うだけの関係で、StoreやReducerの実装を知る必要がありません。これらは完全な疎結合な関係になっています。しかし、実際にテストを書く際は通常のモジュール設定を行うのと同様にStoreとReducerを設定する必要があります。つまりComponentのテストを書く為には必要なStoreとReducerの関係を理解して一つ一つ設定する必要があります。これは本来不要な作業で面倒なだけです。

MockStoreを使用すればこれらの設定をすることなくStoreを使用することができます。

例として、MockStoreを使用しない場合とした場合のComponentのテスト設定を比較してみましょう。

###### MockStoreを使わないComponentのテスト設定
```typescript
TestBed.configureTestingModule({
  imports: [
    StoreModule.forRoot({
      users: combineReducers(userReducers),
    }),
  ],
})
```

Componentのテスト準備としてStoreModuleの設定と関連するReducerの設定が必要になります。  

###### MockStoreを使ったComponentのテスト設定
```typescript
TestBed.configureTestingModule({
  providers: provideMockStore(),
});
```

これだけです。StoreやReducerの設定がなくなり非常にシンプルになります。  


## MockStoreの使い方
では実際にMockStoreの使用方法を見ていきましょう。  
基本的にStateを設定するだけのモジュールなので、初期値設定と値変更の2種類の機能しかありません。  

### Importの設定
MockStoreは `@ngrx/store/testing` のモジュールから提供されています。v7を使用している場合は追加でインストールすることなくすぐに利用できます。

```typescript
import { MockStore, provideMockStore } from '@ngrx/store/testing';
```


### Stateの初期値の設定
MockStoreは先程の `provideMockStore` に初期値となるStateを渡すことが出来ます。指定しない場合は `undefined` が初期値として使われます。

```typescript
interface MockStoreConfig<T> {
  initialState?: T;
}

function provideMockStore<T = any>(config: MockStoreConfig<T> = {}): Provider[];
```

実際に以下のように使用することができます。

```typescript
describe('Stateの初期値の設定', () => {
  let mockStore: MockStore<any>;
  const initialState = {
    users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: provideMockStore({ initialState }),
    });

    mockStore = TestBed.get(Store);
  });

  it('Storeの初期値はinitialStateで設定されている', (done: any) => {
    mockStore.subscribe(val => {
      expect(val).toEqual(initialState);
      done();
    });
  });
});
```

この記事ではComponentのテストのサンプルコードは割愛しますが、OnInit内で `store.select()` をしている場合はこの初期設定を使用すると良いでしょう。


### Stateの値の変更
MockStoreではReducerを使用しないので、Stateの値の更新方法が変わります。Stateの値を変更したい時はActionをdispatchするのではなく `MockStore.setState` というメソッドを使用して、任意のタイミングで手動でStateを設定します。

```typescript
export class MockStore<T> extends Store<T> {
  setState(nextState: T): void;
}
```

実際に以下のように使用することができます。

```typescript
describe('Stateの値の変更', () => {
  let mockStore: MockStore<any>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: provideMockStore(),
    });

    mockStore = TestBed.get(Store);
  });

  it('Stateの値はsetStateを使用していつでも変更することが出来る', (done: any) => {
    const newState = {
      users: [{ id: 3, name: 'user3' }, { id: 4, name: 'user4' }],
    };

    mockStore.setState(newState);

    mockStore.subscribe(val => {
      expect(val).toEqual(newState);
      done();
    });
  });
});
```

なかなか直感的に使用することが出来ます。
Stateの状態ごとにComponentをテストする際などだいぶ見通し良く書くことが出来るようになるかと思います。

## おわりに
以上がMockStoreの機能になります。
この記事では概要までの紹介として、実際にComponentのテストの書き方などは別の記事で書ければと思います。

余談ですが `provideMockStore` のような機能がEffectsにも存在していて、 `provideMockActions` という関数を使うことでEffectsのテストを簡単に書けたりします。
NgRxは非常にテストが書きやすい設計になっていて個人的にとても良いライブラリだと思います。MockStore、ぜひこれから使用していってください。

明日19日目の記事は[aoshi](https://qiita.com/aoshi)さんです！