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