ค้นหาเว็บไซต์

การทดสอบหน่วย - วิธีจำลองโมดูลที่นำเข้าโดยไม่มีเมธอดใน Angular?


ฉันมีแอป Angular พร้อมกับกรอบการทดสอบ Jasmine แอปนี้มีบริการที่เรียกว่า AuthService ซึ่งจัดการการถอดรหัส JSON Web Tokens:

auth.service.ts

import * as jwtDecode from 'jwt-decode';
...

@Injectable()
export class AuthService {
  ...
  public getTokenPayload(token) {
    return jwtDecode(token);
  }
}

ตอนนี้ฉันต้องการที่จะทำการสตับโมดูล jwtDecode และส่งค่าปลอมเพื่อวัตถุประสงค์ในการทดสอบ:

auth.service.spec.ts

...

it('should get a token payload', () => {
  const fakeToken = 'fake-token';

  spyOn(service, 'getTokenPayload').and.callThrough();
  const tokenPayload = service.getTokenPayload(fakeToken);

  expect(tokenPayload).toBe('fake-token');
});

เนื่องจาก 'fake-token' ไม่ใช่ JSON Web Token ที่ถูกต้อง การทดสอบของฉันจึงล้มเหลวพร้อมข้อความว่า:

InvalidTokenError: Invalid token specified: undefined is not an object (evaluating 'str.replace')

นี่อาจเป็นข้อผิดพลาดที่เกิดจากโมดูล jwt-decode ซึ่งถือว่าเป็นเรื่องที่คาดไว้ ฉันไม่ต้องการที่จะต้องรวมโมดูลอื่นเพียงเพื่อสร้าง JSON Web Tokens ที่ถูกต้องสำหรับการทดสอบ แทนที่จะเป็นเช่นนั้น ฉันต้องการที่จะจำลองการทำงานของ jwtDecode()

สิ่งที่ฉันได้ลอง

1. การใช้ spyOn บน jwtDecode

เมื่อฉันใช้ spyOn ฉันต้องการอ็อบเจ็กต์ที่มีเมธอด ดังนั้นสำหรับ jwtDecode ที่ถูกนำเข้า วิธีนี้จะใช้ไม่ได้ เพราะมันเป็นฟังก์ชันโดยตัวมันเอง:

spyOn(jwtDecode, '<method?>').and.callFake(() => 'fake-token');

2. การใช้ callFake กับ getTokenPayload

ฉันได้ลองใช้:

spyOn(service, 'getTokenPayload').and.callFake(() => 'fake-token');

...และนั่นช่วยป้องกันไม่ให้เกิดข้อผิดพลาดใด ๆ อย่างไรก็ตาม รายงานความครอบคลุมโค้ดของฉันตอนนี้แสดงว่าฟังก์ชัน getTokenPayload ยังไม่ได้ถูกครอบคลุม นอกจากนี้ ฉันยังมีฟังก์ชันอื่นในแอปพลิเคชันที่ใช้โมดูล NPM ภายนอก และฉันไม่ต้องการละเว้นความครอบคลุมโค้ดเพราะพวกมันอาจมีการทำงานอื่นภายในเมธอดที่ควรถูกทดสอบ

3. การใช้ createSpy บน jwtDecode

ฉันพยายามเขียนทับ jwtDecode ที่นำเข้าและสร้างสไปย์:

const jwtDecode = jasmine.createSpy('jwtDecode').and.returnValue('fake-token');

นี่ทำให้ฉันได้รับข้อผิดพลาดเหมือนข้างต้น ซึ่งบ่งชี้ว่า jwtDecode ไม่ได้ถูกแทนที่ภายใน AuthService ของฉันจริง ๆ

4. การใช้ window เป็นวัตถุ

จากคำถามนี้ ฉันอ่านว่าโมดูลทั่วโลกอาจถูกแนบกับวัตถุ window ดังนั้นฉันจึงลองทำสิ่งเดียวกันสำหรับ jwt-decode:

ภายในการทดสอบ...

console.log(window.jwtDecode); // undefined
console.log(window.jwt_decode); // undefined

น่าเสียดาย ทั้งสองค่าคือ undefined บนวัตถุ window

คำถาม

ฉันคิดว่าโดยทั่วไปแล้วคำถามจะกลายเป็น:

จะทำการ stub โมดูล NPM ที่นำเข้าอย่างไร? โดยเฉพาะอย่างยิ่ง จะทำการ stub มันอย่างไรถ้ามันไม่ใช่อ็อบเจ็กต์ แต่เป็นฟังก์ชัน (โดยที่ไม่มีเมธอดให้ใช้ใน Jasmine spyOn)?

คำตอบ #1

คุณใกล้ความสำเร็จมาก! เนื่องจากบริการคือเพียงคลาส วิธีที่ดีที่สุดในการทดสอบคือการสร้างอินสแตนซ์ใหม่และสอดส่องมัน เหมือนกับที่คุณกำลังทำอยู่ หากคุณต้องการสอดส่องเมธอดที่นำเข้า คุณจะต้องรวมมันเข้ากับบริการของคุณ มิฉะนั้น การทดสอบของคุณจะไม่สามารถรู้ได้ว่าเมธอดนั้นคืออะไร

ดังนั้นให้มีคุณสมบัติบางอย่างในบริการของคุณ:

jwtDecode = jwtDecode; // the imported one

และเรียกมันว่า this.jwtDecode ในเมธอด getTokenPayload ของคุณ

จากนั้นสิ่งต่อไปนี้จะได้ผล:

const service = new AuthService( /* constructor args */ );

const jwtDecode = spyOn(service, 'jwtDecode');
jwtDecode.and.returnValue('fake-token');

คำตอบ #2

เพียงแค่สอดแนมการส่งออก default จากโมดูล jwt-decode ซึ่งก็คือฟังก์ชัน jwtDecode สิ่งนี้จะช่วยให้คุณไม่ต้องทำให้บริการของคุณปนเปื้อนตามที่แนะนำ

ตัวอย่างเช่น ในไฟล์สเปคของคุณ:

import * as jwt from 'jwt-decode';
...
// return whatever data structure you require
spyOn(jwt, 'default').and.returnValue({ someData: ['x', 'y', 'z'] });

บทความที่เกี่ยวข้อง