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

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

Takahiro Iwasa
Takahiro Iwasa
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 が利用できます。

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

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.