Sahtecilik (Mocking)
Test yazarken, dahili veya harici bir hizmetin "sahte" bir sürümünü oluşturmanız gerekebilir. Bu işlem genellikle "mocking" (sahtecilik) olarak adlandırılır. Vitest, vi yardımcısı aracılığıyla size bu konuda yardımcı olacak işlevler sunar. import { vi } from 'vitest'
ile içe aktarabilir veya küresel yapılandırma etkinleştirildiğinde global olarak erişebilirsiniz.
WARNING
Testler arasındaki sahte durum değişikliklerini önlemek için her testten önce veya sonra sahteleri temizlemeyi veya geri yüklemeyi unutmayın! Daha fazla bilgi için mockReset
belgelerine bakın.
Hemen başlamak isterseniz, API bölümüne göz atın, aksi takdirde sahtecilik dünyasına daha derinlemesine dalmak için okumaya devam edin.
Tarihler
Bazen test sırasında tutarlılığı sağlamak için tarihin kontrolünü elinizde tutmanız gerekebilir. Vitest, zamanlayıcıları ve sistem tarihini manipüle etmek için @sinonjs/fake-timers
paketini kullanır. Belirli API hakkında daha fazla ayrıntıyı burada bulabilirsiniz.
Örnek
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const businessHours = [9, 17];
function purchase() {
const currentHour = new Date().getHours();
const [open, close] = businessHours;
if (currentHour > open && currentHour < close) return { message: 'Success' };
return { message: 'Error' };
}
describe('satın alma akışı', () => {
beforeEach(() => {
// Vitest'e sahte zamanlayıcılar kullanacağımızı belirt
vi.useFakeTimers();
});
afterEach(() => {
// Her test çalıştırmasından sonra tarihi geri yükle
vi.useRealTimers();
});
it('iş saatleri içinde satın almalara izin verir', () => {
// İş saatleri içinde saati ayarla
const date = new Date(2000, 1, 1, 13);
vi.setSystemTime(date);
// Date.now()'a erişim yukarıda ayarlanan tarihle sonuçlanacaktır
expect(purchase()).toEqual({ message: 'Success' });
});
it('iş saatleri dışında satın almalara izin vermez', () => {
// İş saatleri dışında saati ayarla
const date = new Date(2000, 1, 1, 19);
vi.setSystemTime(date);
// Date.now()'a erişim yukarıda ayarlanan tarihle sonuçlanacaktır
expect(purchase()).toEqual({ message: 'Error' });
});
});
Fonksiyonlar
Fonksiyonları sahteleme iki farklı kategoriye ayrılabilir: izleme (spying) ve taklit etme (mocking).
Bazen tek ihtiyacınız olan belirli bir fonksiyonun çağrılıp çağrılmadığını (ve muhtemelen hangi argümanların geçirildiğini) doğrulamaktır. Bu durumlarda, doğrudan vi.spyOn()
ile kullanabileceğimiz bir gözetleyici yeterli olacaktır (buradan daha fazla bilgi edinin).
Ancak gözetleyiciler yalnızca fonksiyonları izlemenize yardımcı olabilir, bu fonksiyonların uygulamasını değiştiremezler. Bir fonksiyonun sahte (veya taklit edilmiş) bir sürümünü oluşturmamız gerektiği durumlarda, vi.fn()
kullanabiliriz (buradan daha fazla bilgi edinin).
Fonksiyonları sahteleme için bir temel olarak Tinyspy kullanıyoruz, ancak onu jest
uyumlu hale getirmek için kendi sarmalayıcımız var. Hem vi.fn()
hem de vi.spyOn()
aynı metotları paylaşır, ancak yalnızca vi.fn()
'nin dönüş sonucu çağrılabilir.
Örnek
import { afterEach, describe, expect, it, vi } from 'vitest';
function getLatest(index = messages.items.length - 1) {
return messages.items[index];
}
const messages = {
items: [
{ message: 'Simple test message', from: 'Testman' },
// ...
],
getLatest, // `getter` veya `setter` özellikleri destekleniyorsa kullanılabilir
};
describe('mesajları okuma', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('bir spy ile en son mesajı almalı', () => {
const spy = vi.spyOn(messages, 'getLatest');
expect(spy.getMockName()).toEqual('getLatest');
expect(messages.getLatest()).toEqual(
messages.items[messages.items.length - 1]
);
expect(spy).toHaveBeenCalledTimes(1);
spy.mockImplementationOnce(() => 'access-restricted');
expect(messages.getLatest()).toEqual('access-restricted');
expect(spy).toHaveBeenCalledTimes(2);
});
it('bir mock ile almalı', () => {
const mock = vi.fn().mockImplementation(getLatest);
expect(mock()).toEqual(messages.items[messages.items.length - 1]);
expect(mock).toHaveBeenCalledTimes(1);
mock.mockImplementationOnce(() => 'access-restricted');
expect(mock()).toEqual('access-restricted');
expect(mock).toHaveBeenCalledTimes(2);
expect(mock()).toEqual(messages.items[messages.items.length - 1]);
expect(mock).toHaveBeenCalledTimes(3);
});
});
Daha Fazla
Global Değişkenler
vi.stubGlobal
yardımcısını kullanarak jsdom
veya node
ile mevcut olmayan global değişkenleri sahteleştirebilirsiniz. Global değişkenin değerini bir globalThis
nesnesine yerleştirecektir.
import { vi } from 'vitest';
const IntersectionObserverMock = vi.fn(() => ({
disconnect: vi.fn(),
observe: vi.fn(),
takeRecords: vi.fn(),
unobserve: vi.fn(),
}));
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
// artık `IntersectionObserver` veya `window.IntersectionObserver` olarak erişebilirsiniz
Modüller
Sahte modüller, bazı diğer kodlarda çağrılan üçüncü parti kütüphaneleri gözlemlemenizi ve argümanları, çıktıyı test etmenize veya hatta uygulamasını yeniden tanımlamanıza olanak tanır.
Daha ayrıntılı bir API açıklaması için vi.mock()
api bölümüne bakın.
Otomatik Sahteleme Algoritması
Kodunuz, bu modül için herhangi bir ilişkili __mocks__
dosyası veya factory
olmadan sahte bir modülü içe aktarıyorsa, Vitest modülün kendisini sahteleyecektir. Bu işlem, modülü çağırarak ve her dışa aktarmayı sahteleyerek gerçekleştirilir.
Aşağıdaki prensipler geçerlidir:
- Tüm diziler boşaltılacaktır
- Tüm ilkel (primitive) ve koleksiyonlar aynı kalacaktır
- Tüm nesneler derinlemesine klonlanacaktır
- Tüm sınıf örnekleri ve prototipleri derinlemesine klonlanacaktır
Örnek
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';
// işleyiciler
export function success(data) {}
export function failure(data) {}
// todoları al
export async function getTodos(event, context) {
const client = new Client({
// ...clientOptions
});
await client.connect();
try {
const result = await client.query('SELECT * FROM todos;');
client.end();
return success({
message: `${result.rowCount} item(s) returned`,
data: result.rows,
status: true,
});
} catch (e) {
console.error(e.stack);
client.end();
return failure({ message: e, status: false });
}
}
vi.mock('pg', () => {
const Client = vi.fn();
Client.prototype.connect = vi.fn();
Client.prototype.query = vi.fn();
Client.prototype.end = vi.fn();
return { Client };
});
vi.mock('./handlers.js', () => {
return {
success: vi.fn(),
failure: vi.fn(),
};
});
describe('bir todo öğesi listesi alma', () => {
let client;
beforeEach(() => {
client = new Client();
});
afterEach(() => {
vi.clearAllMocks();
});
it('öğeleri başarıyla döndürmeli', async () => {
client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
await getTodos();
expect(client.connect).toBeCalledTimes(1);
expect(client.query).toBeCalledWith('SELECT * FROM todos;');
expect(client.end).toBeCalledTimes(1);
expect(success).toBeCalledWith({
message: '0 item(s) returned',
data: [],
status: true,
});
});
it('bir hata fırlatmalı', async () => {
const mError = new Error('Unable to retrieve rows');
client.query.mockRejectedValueOnce(mError);
await getTodos();
expect(client.connect).toBeCalledTimes(1);
expect(client.query).toBeCalledWith('SELECT * FROM todos;');
expect(client.end).toBeCalledTimes(1);
expect(failure).toBeCalledWith({ message: mError, status: false });
});
});
İstekler
Vitest Node'da çalıştığı için, ağ isteklerini taklit etmek zordur; web API'leri kullanılamaz, bu nedenle ağ davranışını simüle edecek bir mekanizmaya ihtiyacımız var. Bunu başarmak için Mock Service Worker öneriyoruz. Hem REST
hem de GraphQL
ağ isteklerini sahtelemenize olanak tanır ve çerçeveden bağımsızdır.
Mock Service Worker (MSW), testlerinizin yaptığı istekleri engelleyerek çalışır ve herhangi bir uygulama kodunuzu değiştirmeden kullanmanıza olanak tanır. Tarayıcıda bu, Service Worker API kullanır. Node.js'de ve Vitest için node-request-interceptor kullanır. MSW hakkında daha fazla bilgi edinmek için giriş belgesini okuyun.
Yapılandırma
Yapılandırma dosyanızda aşağıdaki gibi kullanabilirsiniz:
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { graphql, rest } from 'msw';
const posts = [
{
userId: 1,
id: 1,
title: 'first post title',
body: 'first post body',
},
// ...
];
export const restHandlers = [
rest.get('https://rest-endpoint.example/path/to/posts', (req, res, ctx) => {
return res(ctx.status(200), ctx.json(posts));
}),
];
const graphqlHandlers = [
graphql.query(
'https://graphql-endpoint.example/api/v1/posts',
(req, res, ctx) => {
return res(ctx.data(posts));
}
),
];
const server = setupServer(...restHandlers, ...graphqlHandlers);
// Tüm testlerden önce sunucuyu başlat
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Tüm testlerden sonra sunucuyu kapat
afterAll(() => server.close());
// Her testten sonra işleyicileri sıfırla (`test izolasyonu için önemli`)
afterEach(() => server.resetHandlers());
Sunucuyu
onUnhandleRequest: 'error'
ile yapılandırmak, eşleşen bir istek işleyicisi olmayan bir istek olduğunda bir hata fırlatılmasını sağlar.
Örnek
MSW kullanan tam çalışan bir örneğimiz var: MSW ile React Testi.
Daha Fazla
MSW'de çok daha fazlası var. Çerezlere ve sorgu parametrelerine erişebilir, sahte hata yanıtları tanımlayabilir ve çok daha fazlasını yapabilirsiniz! MSW ile yapabileceğiniz her şeyi görmek için belgelerini okuyun.
Zamanlayıcılar
Zaman aşımlarını veya aralıkları içeren kodu test ettiğimizde, testlerimizin beklemesini veya zaman aşımına uğramasını sağlamak yerine, setTimeout
ve setInterval
çağrılarını sahteleyen "sahte" zamanlayıcılar kullanarak testlerimizi hızlandırabiliriz.
Daha ayrıntılı bir API açıklaması için vi.useFakeTimers
api bölümüne bakın.
Örnek
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
function executeAfterTwoHours(func) {
setTimeout(func, 1000 * 60 * 60 * 2); // 2 saat
}
function executeEveryMinute(func) {
setInterval(func, 1000 * 60); // 1 dakika
}
const mock = vi.fn(() => console.log('executed'));
describe('gecikmeli yürütme', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.restoreAllMocks();
});
it('fonksiyonu yürütmeli', () => {
executeAfterTwoHours(mock);
vi.runAllTimers();
expect(mock).toHaveBeenCalledTimes(1);
});
it('fonksiyonu yürütmemeli', () => {
executeAfterTwoHours(mock);
// 2ms ilerletmek işlevi tetiklemeyecektir
vi.advanceTimersByTime(2);
expect(mock).not.toHaveBeenCalled();
});
it('her dakika yürütmeli', () => {
executeEveryMinute(mock);
vi.advanceTimersToNextTimer();
expect(mock).toHaveBeenCalledTimes(1);
vi.advanceTimersToNextTimer();
expect(mock).toHaveBeenCalledTimes(2);
});
});
Hızlı Başvuru
INFO
Aşağıdaki örneklerdeki vi
doğrudan vitest
ten içe aktarılır. Yapılandırmanızda globals
'ı true
olarak ayarlarsanız, global olarak da kullanabilirsiniz.
Şunları yapmak istiyorum:
- Bir
method
üzerinde izleme yapmak
const instance = new SomeClass();
vi.spyOn(instance, 'method');
- Dışa aktarılmış değişkenleri mocklamak
// some-path.js
export const getter = 'variable';
// some-path.test.ts
import * as exports from './some-path.js';
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked');
- Dışa aktarılmış fonksiyonu mocklamak
vi.mock
ile örnek:
// ./some-path.js
export function method() {}
import { method } from './some-path.js';
vi.mock('./some-path.js', () => ({
method: vi.fn(),
}));
WARNING
vi.mock
çağrısının dosyanın en üstüne taşındığını unutmayın. vi.mock
çağrılarını beforeEach
içine yerleştirmeyin, aksi takdirde bunlardan yalnızca biri bir modülü mocklayacaktır.
vi.spyOn
ile örnek:
import * as exports from './some-path.js';
vi.spyOn(exports, 'method').mockImplementation(() => {});
- Dışa aktarılmış sınıf uygulamasını mocklamak
vi.mock
ve prototip ile örnek:
// some-path.ts
export class SomeClass {}
import { SomeClass } from './some-path.js';
vi.mock('./some-path.js', () => {
const SomeClass = vi.fn();
SomeClass.prototype.someMethod = vi.fn();
return { SomeClass };
});
// SomeClass.mock.instances SomeClass örneklerini içerecektir
vi.mock
ve dönüş değeri ile örnek:
import { SomeClass } from './some-path.js';
vi.mock('./some-path.js', () => {
const SomeClass = vi.fn(() => ({
someMethod: vi.fn(),
}));
return { SomeClass };
});
// SomeClass.mock.returns döndürülen nesneyi içerecektir
vi.spyOn
ile örnek:
import * as exports from './some-path.js';
vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
// ilk iki örnekteki uygun olan uygulamayı seçebilirsiniz
});
- Bir fonksiyondan döndürülen bir nesneyi izlemek
Önbellek kullanarak örnek:
// some-path.ts
export function useObject() {
return { method: () => true };
}
// useObject.js
import { useObject } from './some-path.js';
const obj = useObject();
obj.method();
// useObject.test.js
import { useObject } from './some-path.js';
vi.mock('./some-path.js', () => {
let _cache;
const useObject = () => {
if (!_cache) {
_cache = {
method: vi.fn(),
};
}
// şimdi useObject() her çağrıldığında
// aynı nesne referansını döndürecektir
return _cache;
};
return { useObject };
});
const obj = useObject();
// obj.method some-path.js içinde çağrıldı
expect(obj.method).toHaveBeenCalled();
- Bir modülün bir bölümünü mocklamak
import { mocked, original } from './some-path.js';
vi.mock('./some-path.js', async () => {
const mod = await vi.importActual<typeof import('./some-path.js')>(
'./some-path.js'
);
return {
...mod,
mocked: vi.fn(),
};
});
original(); // orijinal davranışa sahip
mocked(); // bir izleme fonksiyonu
- Geçerli tarihi mocklamak
Date
'in zamanını mocklamak için vi.setSystemTime
yardımcı fonksiyonunu kullanabilirsiniz. Bu değer, farklı testler arasında otomatik olarak sıfırlanmayacaktır.
vi.useFakeTimers
kullanmanın Date
'in zamanını da değiştirdiğine dikkat edin.
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// taklit edilen zamanı sıfırlamak için
vi.useRealTimers();
- Global değişkeni mocklamak
globalThis
'e bir değer atayarak veya vi.stubGlobal
yardımcısını kullanarak global değişken ayarlayabilirsiniz. vi.stubGlobal
kullanırken, unstubGlobals
yapılandırma seçeneğini etkinleştirmediğiniz veya vi.unstubAllGlobals
çağırmadığınız sürece, bu değer farklı testler arasında otomatik olarak sıfırlanmayacaktır.
vi.stubGlobal('__VERSION__', '1.0.0');
expect(__VERSION__).toBe('1.0.0');
import.meta.env
'yi mocklamak
Ortam değişkenini değiştirmek için, ona yeni bir değer atayabilirsiniz. Bu değer, farklı testler arasında otomatik olarak sıfırlanmayacaktır.
import { beforeEach, expect, it } from 'vitest';
// beforeEach kancasında manuel olarak sıfırlayabilirsiniz
const originalViteEnv = import.meta.env.VITE_ENV;
beforeEach(() => {
import.meta.env.VITE_ENV = originalViteEnv;
});
it('değeri değiştirir', () => {
import.meta.env.VITE_ENV = 'staging';
expect(import.meta.env.VITE_ENV).toBe('staging');
});
Değeri otomatik olarak sıfırlamak istiyorsanız, unstubEnvs
yapılandırma seçeneği etkinleştirilmişken vi.stubEnv
yardımcısını kullanabilirsiniz (veya beforeEach
kancasında manuel olarak vi.unstubAllEnvs
çağırın):
import { expect, it, vi } from 'vitest';
// testlerin çalıştırılmasından önce "VITE_ENV" "test"tir
import.meta.env.VITE_ENV === 'test';
it('değeri değiştirir', () => {
vi.stubEnv('VITE_ENV', 'staging');
expect(import.meta.env.VITE_ENV).toBe('staging');
});
it('değer, başka bir test çalıştırılmadan önce eski haline döner', () => {
expect(import.meta.env.VITE_ENV).toBe('test');
});
// vitest.config.ts
export default {
test: {
unstubAllEnvs: true,
},
};