javascript - Jest toHaveBeenNthCalledWith ส่งค่าตัวแปรตามค่าที่ถูกตั้งไว้ในปัจจุบัน แทนที่จะเป็นค่าตอนที่ถูกเรียก
ฉันกำลังพัฒนาแอปพลิเคชัน nestjs โดยใช้ชุดปลั๊กอิน nestjs มาตรฐานทั่วไป (เช่น @nestjs/axios และ @nestjs/testing)
ฉันมีฟังก์ชันที่ค่อนข้างเรียบง่ายซึ่งควรจะแบ่งหน้า (paginate) ผ่านคำขอ API และเพิ่มค่าที่ได้ลงในอาร์เรย์
async syncRecordList(limit: number = 1000) {
const url = 'https://my.test.url/records';
const headers = {
Authorization: 'Bearer token-value',
};
const params = {
limit, // the per page limit
offset: 0, // the offset for pagination
};
const itemIds: number[] = [];
let more: boolean = true;
while (more) {
const response = await this.httpService.axiosRef.get(url, {
headers,
params,
});
response.data.items.forEach((item) => {
itemIds.push(item.id);
});
params.offset += limit;
more = response.data.hasMore;
}
return itemIds;
}
เพื่อทดสอบฟังก์ชันง่าย ๆ นี้ ฉันมีการทดสอบดังต่อไปนี้:
describe('syncRecordList', () => {
it('should call multiple times if there is pagination', async () => {
const response0 = {
data: {
items: [{ id: 1 }, { id: 2 }],
hasMore: true,
},
};
const response1 = {
data: {
items: [{ id: 3 }, { id: 4 }],
hasMore: false,
},
};
const callExpectWith0 = [
'https://my.test.url/records',
{
headers: {
Authorization: 'Bearer token-value',
},
params: {
limit: 2,
offset: 0
},
},
]
const callExpectWith1 = [
'https://my.test.url/records',
{
headers: {
Authorization: 'Bearer token-value',
},
params: {
limit: 2,
offset: 2
},
},
]
const httpSpy = jest.spyOn(service.httpService.axiosRef, 'get')
.mockResolvedValueOnce(response0)
.mockResolvedValueOnce(response1);
await service.syncRecordList(2);
expect(httpSpy).toHaveBeenCalledTimes(2);
expect(httpSpy).toHaveBeenNthCalledWith(1, ...callExpectWith0);
expect(httpSpy).toHaveBeenNthCalledWith(2, ...callExpectWith1);
});
});
อย่างที่คุณเห็นจากโค้ด ฉันคาดว่าการเรียกครั้งแรกจะใช้พารามิเตอร์ limit: 2, offset: 0 และการเรียกครั้งที่สองจะใช้พารามิเตอร์ limit: 2, offset: 2 โค้ดถูกออกแบบให้ค่าพารามิเตอร์เปลี่ยนไปในแต่ละครั้งที่มีการเรียกใช้ลูป เพื่อปรับค่า offset ซึ่งสิ่งนี้เกิดขึ้นก่อนที่ลูปจะสิ้นสุด นั่นหมายความว่าครั้งที่สอง ก่อนที่ค่าของ more จะถูกตั้งค่าเป็น false เราจะเพิ่มค่าพารามิเตอร์ offset เพื่อคาดการณ์การเรียกลูปครั้งต่อไป
ความคาดหวังของฉันคือสิ่งนี้จะผ่าน ในประวัติการโทรของม็อกนั้น ฉันคาดหวังว่าพารามิเตอร์จะแสดงสองค่าที่แตกต่างกัน อย่างไรก็ตาม สิ่งที่ฉันเห็นจริงกลับเป็นความล้มเหลว
expect(jest.fn()).toHaveBeenNthCalledWith(n, ...expected)
n: 1
Expected: "https://my.test.url/records", {"headers": {"Authorization": "Bearer token-value"}, "params": {"limit": 2, "offset": 0}}
Received
-> 1
"https://my.test.url/records",
@@ -2,8 +2,8 @@
"headers": Object {
"Authorization": "Bearer token-value",
},
"params": Object {
"limit": 2,
- "offset": 0,
+ "offset": 4,
},
},
2: "https://my.test.url/records", {"headers": {"Authorization": "Bearer token-value"}, "params": {"limit": 2, "offset": 4}}
ผลลัพธ์ของ console.log(JSON.stringify(httpSpy.mock.calls)); คือ [["https://123456789c.suitetalk.api.netsuite.com/services/rest/record/v1/assemblyItem",{"headers":{"Authorization":"Bearer token-value"},"params":{"limit":2,"offset":4}}],["https://123456789c.suitetalk.api.netsuite.com/services/rest/record/v1/assemblyItem",{"headers":{"Authorization":"Bearer token-value"},"params":{"limit":2,"offset":4}}]] แสดงให้เห็นว่ามันถูกเรียกสองครั้งโดยมี offset เท่ากับสี่
แต่ไม่ได้ถูกเรียกด้วยการเลื่อนสี่ ค่าของ params ในตอนท้ายของการทดสอบมีการเลื่อนสี่
ถ้าฉันเปลี่ยนโค้ดเล็กน้อย เพื่อใช้สำเนาของ params แทน params มันจะทำงาน:
async syncRecordList(type: NetSuiteRecordTypes, integrationId: string, limit: number = 1000) {
// ... Snipped
const params = {
limit, // the per page limit
offset: 0, // the offset for pagination
};
const itemIds: number[] = [];
let more: boolean = true;
while (more) {
// This was done so that testing would pass. The params object was being modified and
// causing the tests to fail as it seems to always refer to the variable used, not what what
// was actually called with.
const paramsToUse = { ...params };
const response = await this.httpService.axiosRef.get(url, {
headers,
params: paramsToUse,
});
response.data.items.forEach((item) => {
itemIds.push(item.id);
});
params.offset += limit;
more = response.data.hasMore;
}
return itemIds;
}
ในกรณีนี้ การทดสอบของฉันผ่านเพราะฟังก์ชันถูกเรียกด้วยสิ่งที่ฉันคาดไว้
ท้ายที่สุดคำถามของฉันคือ มีวิธีที่ Jest เรียกใช้กับ can ที่สามารถใช้สิ่งที่ถูกส่งเข้ามาในขณะแห่งการเรียกจริงๆ หรือไม่ ไม่ใช่ค่าของตัวแปรหลังจากที่การทดสอบเสร็จสิ้น?
คำตอบ #1
นี่เป็นข้อจำกัดของ Jest จริง ๆ ความคิดเห็นในปัญหา Jest GitHub หมายเลข 434 และ 4715 ระบุว่าการคัดลอกค่ามาแทนการใช้การอ้างอิงจะมีค่าใช้จ่ายสูงเกินไปและส่งผลกระทบต่อประสิทธิภาพของ Jest อย่างรุนแรง
จากความคิดเห็นต่อคำถามเดิมของฉัน ดูเหมือนว่าฉันมีตัวเลือกไม่กี่อย่าง
- แก้ไขโค้ดของฉันให้ตัวแปรในลูปมีค่าที่แตกต่างกันเสมอ แทนที่จะเป็นการอ้างอิง
- แก้ไขการทดสอบของฉันเพื่อใช้ assert ที่แตกต่าง
- ใช้ไลบรารีที่แตกต่างกันเพื่อเปลี่ยนการทดสอบนั้น
ในคำถามต้นฉันกำลังทำตัวเลือกที่ 1 อยู่แล้ว แต่ฉันได้ปรับปรุงสิ่งนั้นตามความคิดเห็นต่อคำถาม สุดท้ายฉันก็ได้สิ่งที่ประมาณนี้ในวิธีของฉัน:
async syncRecordList(limit: number = 1000) {
const url = 'https://my.test.url/records';
const headers = { Authorization: 'Bearer token-value' };
const itemIds: number[] = [];
let offset: number = 0;
let more: boolean = true;
while (more) {
const response = await axios.get(url, {
headers,
params: { limit, offset },
});
response.data.items.forEach((item) => {
itemIds.push(item.id);
});
offset += limit;
more = response.data.hasMore;
}
return itemIds;
}
