Jasmine でモックオブジェクトのプロパティをスパイ

Jasmine でモックオブジェクトのプロパティをスパイ

岩佐 孝浩
岩佐 孝浩
3 min read
Angular JavaScript

既にモックされたオブジェクトのプロパティをスパイしようとすると、以下のエラーが表示されるはずです。

Error: <spyOnProperty> : currentUser#get has already been spied upon
Usage: spyOnProperty(<object>, <propName>, [accessType])

Object.getOwnPropertyDescriptor を使用して、このエラーを回避できます。

状況

以下のようなテストコードを実装すると、エラーが発生しました。

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AuthService } from '@core/services/auth.service';

describe('AuthService', () => {

  let service: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {provide: AuthService, useValue: jasmine.createSpyObj('AuthService', [], ['currentUser'])},
      ],
    });

    service = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
  });

  it('should get the currentUser', () => {
    spyOnProperty(service, 'currentUser').and.returnValue({id: 1, name: 'Hello World'});
    // Testing code here...
    expect(service.currentUser).toEqual({id: 1, name: 'Hello World'});
  });

});

解決方法

公式のチュートリアルによれば、 Object.getOwnPropertyDescriptor を使用できます。

You can create a spy object with several properties on it quickly by passing an array or hash of properties as a third argument to createSpyObj. In this case you won’t have a reference to the created spies, so if you need to change their spy strategies later, you will have to use the Object.getOwnPropertyDescriptor approach.

これは Stack Overflow の質問でも言及されていました。

ヘルパー関数

ヘルパー関数を src/app/tests/helper.ts に書きました。

/* eslint-disable-next-line arrow-body-style */
export const spyGetter = <T, K extends keyof T>(target: jasmine.SpyObj<T>, key: K): jasmine.Spy => {
  return Object.getOwnPropertyDescriptor(target, key)?.get as jasmine.Spy;
};

/* eslint-disable-next-line arrow-body-style */
export const spySetter = <T, K extends keyof T>(target: jasmine.SpyObj<T>, key: K): jasmine.Spy => {
  return Object.getOwnPropertyDescriptor(target, key)?.set as jasmine.Spy;
};

テストコード更新

テストコードを更新した後、正常に実行されました。

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AuthService } from '@core/services/auth.service';
import { spyGetter } from '@tests/helper';

describe('AuthService', () => {

  let service: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {provide: AuthService, useValue: jasmine.createSpyObj('AuthService', [], ['currentUser'])},
      ],
    });

    service = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
  });

  it('should get the currentUser', () => {
    spyGetter(service, 'currentUser').and.returnValue({id: 1, name: 'Hello World'});
    // Testing code here...
    expect(service.currentUser).toEqual({id: 1, name: 'Hello World'});
  });

});

差分は以下のとおりです。

--- 	Sun Sep 26 11:05:21 2021 UTC
+++ 	Sun Sep 26 11:05:21 2021 UTC
@@ -1,6 +1,7 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { TestBed } from '@angular/core/testing';
 import { AuthService } from '@core/services/auth.service';
+import { spyGetter } from '@tests/helper';

 describe('AuthService', () => {

@@ -18,7 +19,7 @@
   });

   it('should get the currentUser', () => {
-    spyOnProperty(service, 'currentUser').and.returnValue({id: 1, name: 'Hello World'});
+    spyGetter(service, 'currentUser').and.returnValue({id: 1, name: 'Hello World'});
     // Testing code here...
     expect(service.currentUser).toEqual({id: 1, name: 'Hello World'});
   });

まとめ

モック済みオブジェクトのプロパティをスパイしたい場合、 Object.getOwnPropertyDescriptor が利用できます。

この投稿が、お役に立てば幸いです。

岩佐 孝浩

岩佐 孝浩

Software Developer at KAKEHASHI Inc.
AWS を活用したクラウドネイティブ・アプリケーションの要件定義・設計・開発に従事。 株式会社カケハシで、処方箋データ収集の新たな基盤の構築に携わっています。 Japan AWS Top Engineers 2020-2023