- 投稿日:
親クラスをモックして子クラスの単体テストをしたいときに
確認環境
Env | Ver |
---|---|
@swc/cli | 0.1.65 |
@swc/core | 1.3.105 |
@swc/jest | 0.2.31 |
@types/jest | 29.5.11 |
jest | 29.7.0 |
typescript | 5.3.3 |
サンプルコード
コードのフルセットは以下
https://github.com/Lycolia/typescript-code-examples/tree/main/mocking-base-class-with-extension-class-swc-ts
ディレクトリ構成
src
├─BaseClass
│ └─index.ts
└─ChildrenClass
├─index.ts
└─index.spec.ts
src/BaseClass/index.ts
親クラス。この例は抽象クラスだが別に具象クラスでも何でもいいと思う
export abstract class BaseClass {
constructor(
private baseValue: string,
private ctx: { [key: string]: unknown }
) {}
public hoge<ResultT>(payload: { [key: string]: unknown }) {
// テスト走行時には全て無効な値を流し込むため
// それらの影響で落ちないことの確認のために、オブジェクトの子を意図的に書いている
console.log(this.baseValue, this.ctx.hoge, payload.piyo);
// サンプルコードなので戻り値はスタブ
return {} as ResultT;
}
}
src/ChildrenClass/index.ts
子クラス
import { BaseClass } from '../BaseClass';
export class ChildrenClass extends BaseClass {
constructor(baseValue: string, ctx: { [key: string]: unknown }) {
super(baseValue, ctx);
}
public piyo<ResultT>(payload: { [key: string]: unknown }) {
try {
// このsuper.hoge()をモックして、
// catchの中に流れるかどうかを確認するのが目的
return super.hoge<ResultT>(payload);
} catch (err) {
// 実際はロガーが動くような部分だが、サンプルコードのためconsole.logで代用する
console.log(err);
throw err;
}
}
}
src/ChildrenClass/index.spec.ts
子クラスのテスト
import { ChildrenClass } from '.';
import { BaseClass } from '../BaseClass';
describe('fetch', () => {
it('親のfetchが呼ばれ、親の戻り値が返ってくること', () => {
// 子が正しくreturnしてくるかどうかを確認するための値
const expected = { code: 200 };
// Class.prototypeとやるとモック出来る
const spiedSuperFetch = jest
.spyOn(BaseClass.prototype, 'hoge')
.mockReturnValue(expected);
// 親クラスの処理はモックするので適当な値を入れておく
// この内容が実行されていればテストが落ちるのでテストコードが間違っていることの検証に使える
const inst = new ChildrenClass('aaaa', {} as { [key: string]: unknown });
const actual = inst.piyo({ foo: 123 });
// 親クラスのメソッドが正しい引数で呼ばれたことの確認
expect(spiedSuperFetch).toHaveBeenCalledWith({ foo: 123 });
// 子クラスのメソッドの戻り値が正しいことの確認
expect(actual).toStrictEqual(expected);
});
it('親のfetchで例外が出たときに、ログ出力とリスローがされること', () => {
const expected = Error('ERR');
// 親クラスのメソッドが例外をスローするケースを作る
jest.spyOn(BaseClass.prototype, 'hoge').mockImplementation(() => {
throw expected;
});
// catch句の中でロガーが動いているかどうかの検査用
const spiedConsoleLog = jest.spyOn(console, 'log');
const inst = new ChildrenClass('aaaa', {} as { [key: string]: unknown });
// 例外がリスローされていることを確認
expect(() => {
inst.piyo({ foo: 123 });
}).toThrow(expected);
// ロガーが動いていることを確認
expect(spiedConsoleLog).toHaveBeenCalled();
});
});
- 投稿日:
以下のようなコードを書いたときにmockFnにイベント引数が渡らないが、これをどうにかして取る方法。結論から言うとまともに取れないが、試行錯誤した時のログとして残しておく
const mockFn = jest.fn();
render(<input onChange={mockFn} />);
fireEvent.change(inputElement, { target: { value: 'aaa' } });
確認環境
Env | Ver |
---|---|
@swc/core | 1.3.66 |
@swc/jest | 0.2.26 |
@testing-library/jest-dom | 5.16.5 |
@testing-library/react | 14.0.0 |
jest | 29.5.0 |
react | 18.2.0 |
サンプルコード
これは以下のように書くとfireEvent
の第二引数で指定したtarget.value
の部分だけは一応取れる。
理由としてはfireEvent
がelement.dispatchEvent
を読んでいると思われるためだ。余り深くは追っていないが、react-testing-libraryの実装上は多分そうなっていると思われる。
import { fireEvent, render } from '@testing-library/react';
it('test', () => {
const mockFn = jest.fn((ev) => {
console.log(ev);
});
const { container } = render(<input id="hoge" onChange={mockFn} value={'a'} />);
const element = container.querySelector('input');
if (element === null) throw new Error();
element.dispatchEvent = jest.fn();
fireEvent.change(element, {
target: {
value: 'bbb'
}
});
expect(element.dispatchEvent.mock.instances[0].value).toBe('bbb');
});