javascript - Jest toHaveBeenNthCalledWith, çağrı anındaki değerin yerine, şu an ayarlanmış olan değişken değerini döndürür


Ben, oldukça standart bir dizi NestJS eklentisi kullanarak (@nestjs/axios ve @nestjs/testing gibi şeyler) bir NestJS uygulaması üzerinde çalışıyorum.

API isteklerinde sayfalandırma yapması ve elde edilen değerleri bir diziye eklemesi gereken oldukça basit bir işlevim var.

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;
    }

Bu basit fonksiyonu test etmek için, aşağıdaki testim var:

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);
        });
    });

Koddaki gibi görebileceğiniz üzere, ilk çağrının limit: 2, offset: 0 parametrelerini kullanmasını, ikinci çağrının ise limit: 2, offset: 2 parametrelerini kullanmasını bekliyorum. Kod, döngünün her çağrısında offset’i değiştirmek için params değerinin değiştirilmesi şeklinde tasarlanmıştır. Bu, döngü çıkmadan önce yapılır; yani ikinci sefer olunca, more değeri false olarak ayarlanmadan önce, başka bir döngüye yönelik olarak params offset değeri artırılır.

Benim beklentim bunun geçeceği yönündeydi. Mocks çağrı geçmişinde parametrelerin iki farklı değer göstermesini bekliyordum. Ancak gerçekte gördüğüm şey bir başarısızlık.

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)); sonucunun [["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}}]] olduğunu ve bunun dörtten bir offset ile iki kez çağrıldığını gösterdiğini belirtiyor.

Ama dörtlük bir offset ile çağrılmadı, testin sonunda params değerinin offseti dört.

Eğer kodu biraz değiştirir ve params yerine params'in bir kopyasını kullanırsam, çalışıyor:

    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;
    }

Bu durumda testlerim geçiyor çünkü fonksiyon, beklediğim şekilde çağrılıyor.

Sonuçta sorum şu, Jest çağrıldığında gerçekten çağrıldığı sırada geçirilen değeri kullanabilir mi, test bittikten sonra değişkenin değeri değil?

Cevap #1

Bu gerçekten Jest'in bir sınırlamasıdır. Jest GitHub sorunları 434 ve 4715 ile ilgili yorumlar, değeri referans yerine kopyalamanın çok maliyetli olacağını ve Jest'in performansını ciddi şekilde etkileyeceğini belirtmektedir.

Orijinal soruma gelen yorumlara göre, birkaç seçeneğim olduğu görülüyor.

  1. Döngüdeki argümanların her zaman farklı olmasını, referans değil, şekilde kodumu değiştir.
  2. Testimi farklı bir assert kullanacak şekilde değiştir
  3. O testi değiştirmek için farklı bir kütüphane uygulayın

Orijinal soruda zaten seçenek 1'i yapıyordum, ama soruya yapılan yorumlara dayanarak bunu geliştirdim. Yöntemimde bunun gibi bir şeyle sonuçlandım:

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;
    }