jasmine.createSpyObj với các thuộc tính

Oct 28 2020

Khi chế nhạo các phần phụ thuộc trong các bài kiểm tra Angular, tôi thường tạo một đối tượng gián điệp bằng cách sử dụng jasmine.createSpyObj:

const serviceSpy= jasmine.createSpyObj('MyService', ['method']);

sau đó cung cấp nó cho TestBed:

  providers: [
    {provide: MyService, useValue: serviceSpy}
  ]

Khi tôi sử dụng nó trong thử nghiệm của mình, tôi có thể chỉ định giá trị trả về mong muốn:

serviceSpy.method.and.returnValue(of([...]));

Bây giờ tôi cũng cần phải giả lập các thuộc tính và tôi không thể tìm ra cách nó nên được thực hiện. createSpyObjkhông cho phép định nghĩa tên thuộc tính:

const serviceSpy= jasmine.createSpyObj('MyService', ['method'], ['property']);

nhưng tôi đã thử các giải pháp khác nhau dựa trên nhiều bài báo và câu trả lời trên mạng mà không thành công, ví dụ:

// Cannot read property 'and' of undefined    
serviceSpy.property.and.returnValue(true);  
// not declared configurable
spyOnProperty(serviceSpy, 'property').and.returnValue(true);  
// no build errors, but value stays 'undefined'
serviceSpy.property = true;  

Cách duy nhất tôi có thể làm cho nó hoạt động được một nửa là:

let fakeValue = true;
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], {'property': fakeValue});

Vấn đề ở đây là nó được thiết lập một lần khi tạo. Nếu tôi muốn thay đổi giá trị mong đợi trong bài kiểm tra, nó không hoạt động.

fakeValue = false;
serviceSpy.property ==> stays to the initial value 'true';

Có tồn tại một giải pháp cho cả các phương thức và thuộc tính giả bằng cách tạo một đối tượng gián điệp hay tôi nên tạo lớp giả của riêng mình để sau đó tôi có thể sử dụng spyOnspyOnProperty?

Tôi cũng muốn biết cách sử dụng của mảng thuộc tính trong createSpyObjđịnh nghĩa. Cho đến nay tôi chưa thấy bất kỳ ví dụ nào trên web giải thích điều đó.

Trả lời

5 jonrsharpe Oct 28 2020 at 01:29

Theo tài liệu (tôi nhấn mạnh):

Bạn có thể tạo một đối tượng gián điệp với một số thuộc tính trên đó một cách nhanh chóng bằng cách chuyển một mảng hoặc băm thuộc tính làm đối số thứ ba createSpyObj. Trong trường hợp này, bạn sẽ không có tham chiếu đến các điệp viên đã tạo, vì vậy nếu bạn cần thay đổi chiến lược gián điệp của họ sau này, bạn sẽ phải sử dụng Object.getOwnPropertyDescriptorcách tiếp cận .

it("creates a spy object with properties", function() {
  let obj = createSpyObj("myObject", {}, { x: 3, y: 4 });
  expect(obj.x).toEqual(3);

  Object.getOwnPropertyDescriptor(obj, "x").get.and.returnValue(7);
  expect(obj.x).toEqual(7);
});

Thuộc tính gián điệp là các bộ mô tả (xem ví dụ Object.definePropertytrên MDN ), vì vậy để truy cập các đối tượng gián điệp, bạn cần lấy đối tượng bộ mô tả sau đó tương tác với getsetcác phương thức được xác định trên đó.


Trong TypeScript, trình biên dịch cần một chút trợ giúp. createSpyObjtrả về hoặc anyhoặc SpyObj<T>và a SpyObjchỉ xác định các phương thức được theo dõi:

type SpyObj<T> = T & {
    [K in keyof T]: T[K] extends Func ? T[K] & Spy<T[K]> : T[K];
               // |     if it's a     |    spy on it     | otherwise leave
               // |     callable      |                  | it alone
};

Vì vậy, để truy cập .andtrên getter của trình mô tả, bạn sẽ cần chuỗi tùy chọn ( Object.getOwnPropertyDescriptorcó thể trả về undefined) và một xác nhận kiểu cho Spy:

(Object.getOwnPropertyDescriptor(obj, "x")?.get as Spy<() => number>).and.returnValue(7);

Sân chơi

1 IanGrainger Nov 11 2020 at 17:57

Cảm ơn bạn rất nhiều @jonrsharpe! Tôi vừa thêm một hàm để tôi có thể làm:

spyPropertyGetter(spy, 'propName').and.returnValue(...)

Được định nghĩa là:

function spyPropertyGetter(
  spyObj: jasmine.SpyObj<any>,
  propName: string
) {
  return Object.getOwnPropertyDescriptor(spyObj, propName).get as jasmine.Spy<
    jasmine.Func
  >;
}