2018-8-9

DIに関する覚書

雑な技術メモ

ユニットテストしやすくするために、クラス内で他クラスのインスタンスを生成するのでなく、コンストラクタの引数としてインスタンスを受け取ろうぜっていうアレ

実際に VRChat の API を叩くサンプルプロジェクトを作って試してみた。

GitHub

DI なし

import { VrcApi } from './vrcapi';
import { config } from './data/config';

export class NotDI {
    private _api: VrcApi;

    constructor() {
        this._api = new VrcApi(
            config.username,
            config.password
        );
    }

    async fetchUserId(username: string) {
        await this._api.init();
        const userData =  await this._api.fetchUserByName(username);
        return userData.id;
    }
}

DI 無しの場合はこのようにコンストラクタ内で VRChat の API を扱うためのクラスVrcApiをインスタンス化することになる。

この場合、ユニットテストの際も常に VRChatAPI 本番環境に直接通信を行うようになってしまう。(一応フラグとかを設定して、フラグによってモックにつなぎにいくみたいな形にすることは可能なのかな?)

DI 有り

import { AbstractVrcApi } from './vrcapi';

export class DI {
    constructor(private _api: AbstractVrcApi) {}

    async fetchUserId(username: string) {
        await this._api.init();
        const userData =  await this._api.fetchUserByName(username);
        return userData.id;
    }
}

DI 有りの場合、上記のように抽象クラスであるAbstractVrcApiのインスタンスをコンストラクタの引数として受け取るようにしている。

こうしておくと、

import { VrcApiMock } from '../src/vrcapi';
import { DI } from '../src/di';

describe('NotDI', () => {
    test('Get User ID', () => {
        const api = new VrcApiMock();
        const notDI = new DI(api);
        return notDI.fetchUserId('nekomasu')
            .then((id) => {
                expect(id).toBe('usr_d0d44753-45d6-4c8e-b7de-ba9c53cb31a1');
            });
    });
});

このように、ユニットテストする際にモッククラスであるVrcApiMockのインスタンスを渡すことで、ユニットテスト時にモックサーバー等への通信によるテストを行うことが出来る。

その他

Jest でテスト時にCross origin null forbiddenというエラー

jsdom により、ブラウザ環境をシミュレートしてテストが実行されているため、クロスオリジン通信をしようとしているとエラーが出てしまう。

node 環境の場合は、jest.config.jstestEnvironmentnodeを設定すれば問題ない。

公式ドキュメント

VRChat の username と displayName は異なる

普段表示されているのは displayName、大文字で登録したものは小文字として username に登録されるっぽい。

displayName: Nekomasu
username: nekomasu