Jak przetestować integrację zestawu Azure SDK w aplikacjach JavaScript przy użyciu platformy Jest
Testowanie kodu integracji dla zestawu Azure SDK dla języka JavaScript jest niezbędne do zapewnienia prawidłowej interakcji aplikacji z usługami platformy Azure. W tym przewodniku pokazano, jak skutecznie przetestować integrację zestawu Azure SDK w twoich aplikacjach JavaScript przy użyciu frameworka testowego.
Podejmując decyzję, czy wyśmiewać wywołania zestawu SDK usługi w chmurze, czy używać usługi na żywo do celów testowych, ważne jest, aby wziąć pod uwagę kompromis między szybkością, niezawodnością i kosztami. W tym artykule pokazano, jak używać platformy Jest jako platformy testowej do testowania integracji zestawu SDK. Można również użyć innych porównywalnych platform testowych.
Wymagania wstępne
Pozorowanie usług w chmurze
Zalety:
- Przyspiesza zestaw testów, eliminując opóźnienie sieci.
- Zapewnia przewidywalne i kontrolowane środowiska testowe.
- Łatwiej symulować różne scenariusze i przypadki brzegowe.
- Zmniejsza koszty związane z używaniem usług w chmurze na żywo, zwłaszcza w potokach ciągłej integracji.
Wady:
- Makiety mogą odbiegać od rzeczywistego zestawu SDK, co prowadzi do rozbieżności.
- Może ignorować niektóre funkcje lub zachowania usługi na żywo.
- Mniej realistyczne środowisko w porównaniu z produkcją.
Korzystanie z usługi na żywo
Zalety:
- To realistyczne środowisko, które ściśle odzwierciedla produkcję.
- Jest przydatne w przypadku testów integracji w celu zapewnienia współpracy różnych części systemu.
- Pomocne jest zidentyfikowanie problemów związanych z niezawodnością sieci, dostępnością usługi i rzeczywistą obsługą danych.
Wady:
- Jest wolniejsza z powodu wywołań sieciowych.
- Jest droższa ze względu na potencjalne koszty użycia usługi.
- Skonfigurowanie i utrzymanie środowiska usług na żywo zgodnego z produkcją jest skomplikowane i czasochłonne.
Wybór między wyśmiewaniem i używaniem usług na żywo zależy od strategii testowania. W przypadku testów jednostkowych, w których szybkość i kontrola są najważniejsze, pozorowanie jest często lepszym wyborem. W przypadku testów integracji, w których realizm ma kluczowe znaczenie, użycie usługi na żywo może zapewnić dokładniejsze wyniki. Równoważenie tych podejść pomaga osiągnąć kompleksowy zakres testów podczas zarządzania kosztami i utrzymania wydajności testów.
Dublety testowe: Makiety, wycinki i podróbki
Test podwójny to jakikolwiek zamiennik używany zamiast czegoś rzeczywistego do celów testowych. Wybrany typ jest oparty na tym, co ma zostać zamieniony. Termin makiety jest często oznaczany jako każdy podwójny , gdy termin jest używany dorywczo. W tym artykule termin jest używany specjalnie i zilustrowany specjalnie w strukturze testowej Jest.
Makiety
Makiety (nazywane również szpiegami): Zastąp funkcję i może kontrolować i szpiegować zachowanie tej funkcji, gdy jest wywoływany pośrednio przez inny kod.
W poniższych przykładach masz 2 funkcje:
-
someTestFunction: funkcja, którą należy przetestować. Wywołuje zależność ,
dependencyFunction
której nie zapisano i nie trzeba jej testować. - dependencyFunctionMock: mock zależności.
// setup
const dependencyFunctionMock = jest.fn();
// perform test
// Jest replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// verify behavior
expect(dependencyFunctionMock).toHaveBeenCalled();
Celem testu jest upewnienie się, że funkcja someTestFunction działa prawidłowo bez wywoływania kodu zależności. Test sprawdza, czy wywołano pozorowanie zależności.
Pozorowanie dużych i małych zależności
Gdy zdecydujesz się wyśmiewać zależność, możesz wyśmiewać tylko to, czego potrzebujesz, takich jak:
- Funkcja lub dwie z większej zależności. Jest oferuje częściowe makiety do tego celu.
- Wszystkie funkcje mniejszej zależności, jak pokazano w przykładzie w tym artykule.
Klasy zastępcze
Celem stubu jest zastąpienie zwracanych danych funkcji, aby symulować różne scenariusze. Możesz użyć wycinku, aby umożliwić kodowi wywoływanie funkcji i odbieranie różnych stanów, w tym pomyślnych wyników, niepowodzeń, wyjątków i przypadków brzegowych. Weryfikacja stanu gwarantuje, że kod obsługuje te scenariusze poprawnie.
// ARRANGE
const dependencyFunctionMock = jest.fn();
const fakeDatabaseData = {first: 'John', last: 'Jones'};
dependencyFunctionMock.mockReturnValue(fakeDatabaseData);
// ACT
// date is returned by mock then transformed in SomeTestFunction()
const { name } = someTestFunction()
// ASSERT
expect(name).toBe(`${first} ${last}`);
Celem powyższego testu jest zapewnienie, że praca wykonana przez someTestFunction
program spełnia oczekiwany wynik. W tym prostym przykładzie zadaniem funkcji jest połączenie imienia i nazwiska rodzin. Korzystając z fałszywych danych, znasz oczekiwany wynik i możesz sprawdzić, czy funkcja wykonuje pracę prawidłowo.
Podróbki
Podróbki zastępują funkcjonalność, której zwykle nie używa się w środowisku produkcyjnym, takich jak używanie bazy danych w pamięci zamiast bazy danych w chmurze.
// fake-in-mem-db.spec.ts
class FakeDatabase {
private data: Record<string, any>;
constructor() {
this.data = {};
}
save(key: string, value: any): void {
this.data[key] = value;
}
get(key: string): any {
return this.data[key];
}
}
// Function to test
function someTestFunction(db: FakeDatabase, key: string, value: any): any {
db.save(key, value);
return db.get(key);
}
// Jest test suite
describe('someTestFunction', () => {
let fakeDb: FakeDatabase;
let testKey: string;
let testValue: any;
beforeEach(() => {
fakeDb = new FakeDatabase();
testKey = 'testKey';
testValue = {
first: 'John',
last: 'Jones',
lastUpdated: new Date().toISOString(),
};
// Spy on the save method
jest.spyOn(fakeDb, 'save');
});
afterEach(() => {
// Clear all mocks
jest.clearAllMocks();
});
test('should save and return the correct value', () => {
// Perform test
const result = someTestFunction(fakeDb, testKey, testValue);
// Verify state
expect(result).toEqual(testValue);
expect(result.first).toBe('John');
expect(result.last).toBe('Jones');
expect(result.lastUpdated).toBe(testValue.lastUpdated);
// Verify behavior
expect(fakeDb.save).toHaveBeenCalledWith(testKey, testValue);
});
});
Celem powyższego testu jest zapewnienie prawidłowej someTestFunction
interakcji z bazą danych. Korzystając z fałszywej bazy danych w pamięci, można przetestować logikę funkcji bez polegania na rzeczywistej bazie danych, dzięki czemu testy będą szybsze i bardziej niezawodne.
Scenariusz: Wstawianie dokumentu do usługi Cosmos DB przy użyciu zestawu Azure SDK
Załóżmy, że masz aplikację, która musi napisać nowy dokument w usłudze Cosmos DB , jeśli wszystkie informacje są przesyłane i weryfikowane. Jeśli zostanie przesłany pusty formularz lub informacje nie są zgodne z oczekiwanym formatem, aplikacja nie powinna wprowadzać danych.
Usługa Cosmos DB jest używana jako przykład, jednak koncepcje dotyczą większości zestawów SDK platformy Azure dla języka JavaScript. Poniższa funkcja przechwytuje tę funkcję:
// insertDocument.ts
import { Container } from '../data/connect-to-cosmos';
import {
DbDocument,
DbError,
RawInput,
VerificationErrors,
} from '../data/model';
import { inputVerified } from '../data/verify';
export async function insertDocument(
container: Container,
doc: RawInput,
): Promise<DbDocument | DbError | VerificationErrors> {
const isVerified: boolean = inputVerified(doc);
if (!isVerified) {
return { message: 'Verification failed' } as VerificationErrors;
}
try {
const { resource } = await container.items.create({
id: doc.id,
name: `${doc.first} ${doc.last}`,
});
return resource as DbDocument;
} catch (error: any) {
if (error instanceof Error) {
if ((error as any).code === 409) {
return {
message: 'Insertion failed: Duplicate entry',
code: 409,
} as DbError;
}
return { message: error.message, code: (error as any).code } as DbError;
} else {
return { message: 'An unknown error occurred', code: 500 } as DbError;
}
}
}
Uwaga
Typy TypeScript ułatwiają definiowanie rodzajów danych używanych przez funkcję. Chociaż nie musisz korzystać z TypeScript do używania Jest lub innych frameworków testowania JavaScript, jest on niezbędny do pisania JavaScript z bezpieczeństwem typów.
Funkcje w tej aplikacji to:
Function | opis |
---|---|
insertDocument | Wstawia dokument do bazy danych. To jest to, co chcemy przetestować. |
inputVerified | Weryfikuje dane wejściowe względem schematu. Zapewnia, że dane są w poprawnym formacie (na przykład prawidłowe adresy e-mail, poprawnie sformatowane adresy URL). |
cosmos.items.create | Funkcja zestawu SDK dla usługi Azure Cosmos DB korzystająca z @azure/cosmos. To jest to, co chcemy wyśmiewać. Ma już własne testy obsługiwane przez właścicieli pakietów. Musimy sprawdzić, czy wywołanie funkcji usługi Cosmos DB zostało wykonane i zwróciło dane, jeśli dane przychodzące przeszły weryfikację. |
Instalowanie zależności platformy testowej
W tym artykule jest używana platforma testowa Jest . Istnieją inne struktury testowe, których można również użyć.
W katalogu głównym katalogu aplikacji zainstaluj narzędzie Jest za pomocą następującego polecenia:
npm install jest
Konfigurowanie pakietu do uruchamiania testu
package.json
Zaktualizuj aplikację za pomocą nowego skryptu, aby przetestować nasze pliki kodu źródłowego. Pliki kodu źródłowego są definiowane przez dopasowanie w częściowej nazwie pliku i rozszerzeniu. Jest szuka plików zgodnie ze wspólną konwencją nazewnictwa dla plików testowych: <file-name>.spec.[jt]s
. Ten wzorzec oznacza, że pliki o nazwie podobnej do następujących przykładów są interpretowane jako pliki testowe i uruchamiane przez platformę Jest:
- * .test.js: na przykład math.test.js
- * .spec.js: na przykład math.spec.js
- Pliki znajdujące się w katalogu testów, takie jak testy/math.js
Dodaj skrypt do package.json , aby obsługiwać ten wzorzec pliku testowego za pomocą narzędzia Jest:
"scripts": {
"test": "jest dist",
}
Kod źródłowy języka TypeScript jest generowany w dist
podfolderze. Narzędzie Jest uruchamia .spec.js
pliki znalezione w dist
podfolderze.
Konfigurowanie testu jednostkowego dla zestawu Azure SDK
Jak możemy używać makiety, wycinków i podróbek do testowania funkcji insertDocument ?
- Makiety: potrzebujemy makiety, aby upewnić się, że zachowanie funkcji jest testowane, takie jak:
- Jeśli dane przechodzą weryfikację, wywołanie funkcji Cosmos DB miało miejsce tylko 1 raz
- Jeśli dane nie przechodzą weryfikacji, wywołanie funkcji Cosmos DB nie powiodło się
- Wycinki:
- Przekazane dane są zgodne z nowym dokumentem zwracanym przez funkcję.
Podczas testowania zastanów się nad konfiguracją testu, samym testem i weryfikacją. Jeśli chodzi o język testowy, ta funkcja używa następujących terminów:
- Rozmieszczanie: konfigurowanie warunków testowych
- Działanie: wywołaj funkcję, aby przetestować, znaną również jako system testowy lub SUT
- Potwierdzenie: zweryfikuj wyniki. Wyniki mogą być zachowaniem lub stanem.
- Zachowanie wskazuje funkcjonalność w funkcji testowej, którą można zweryfikować. Przykładem jest wywołanie pewnej zależności.
- State wskazuje dane zwrócone z funkcji.
Narzędzie Jest, podobnie jak w przypadku innych platform testowych, ma szablon pliku testowego do zdefiniowania pliku testowego.
// boilerplate.spec.ts
describe('nameOfGroupOfTests', () => {
beforeEach(() => {
// Setup required before each test
});
afterEach(() => {
// Cleanup required after each test
});
it('should <do something> if <situation is present>', async () => {
// Arrange
// - set up the test data and the expected result
// Act
// - call the function to test
// Assert
// - check the state: result returned from function
// - check the behavior: dependency function calls
});
});
Jeśli używasz makiety w testach, kod szablonu musi używać pozorowania do testowania funkcji bez wywoływania podstawowej zależności używanej w funkcji, takiej jak biblioteki klienta platformy Azure.
Tworzenie pliku testowego
Plik testowy z mockami do symulowania wywołania zależności ma dodatkową konfigurację. Plik testowy zawiera kilka części:
-
import
: Instrukcje importowania umożliwiają używanie lub pozorowanie dowolnego testu. -
jest.mock
: Utwórz domyślne pozorne zachowanie. Każdy test może zmienić się zgodnie z potrzebami. -
describe
: Rodzina grup testowych dlainsert.ts
pliku. -
test
: każdy test plikuinsert.ts
.
Plik testowy obejmuje trzy testy dla insert.ts
pliku, które można podzielić na dwa typy weryfikacji:
Typ weryfikacji | Test |
---|---|
Szczęśliwa ścieżka: should insert document successfully |
Wywołano wyśmiewaną metodę bazy danych i zwrócono zmienione dane. |
Ścieżka błędu: should return verification error if input is not verified |
Sprawdzanie poprawności danych nie powiodło się i zwróciło błąd. |
Ścieżka błędu:should return error if db insert fails |
Wywołano wyśmiewaną metodę bazy danych i zwrócono błąd. |
Poniższy plik testowy Jest pokazuje, jak przetestować funkcję insertDocument .
// insertDocument.test.ts
import { Container } from '../data/connect-to-cosmos';
import { createTestInputAndResult } from '../data/fake-data';
import {
DbDocument,
DbError,
isDbError,
isVerificationErrors,
RawInput,
} from '../data/model';
import { inputVerified } from '../data/verify';
import { insertDocument } from './insert';
// Mock app dependencies for Cosmos DB setup
jest.mock('../data/connect-to-cosmos', () => ({
connectToContainer: jest.fn(),
getUniqueId: jest.fn().mockReturnValue('unique-id'),
}));
// Mock app dependencies for input verification
jest.mock('../data/verify', () => ({
inputVerified: jest.fn(),
}));
describe('insertDocument', () => {
// Mock the Cosmo DB Container object
let mockContainer: jest.Mocked<Container>;
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();
// Mock the Cosmos DB Container create method
mockContainer = {
items: {
create: jest.fn(),
},
} as unknown as jest.Mocked<Container>;
});
it('should return verification error if input is not verified', async () => {
// Arrange - Mock the input verification function to return false
jest.mocked(inputVerified).mockReturnValue(false);
// Arrange - wrong shape of doc on purpose
const doc = { name: 'test' };
// Act - Call the function to test
const insertDocumentResult = await insertDocument(
mockContainer,
doc as unknown as RawInput,
);
// Assert - State verification: Check the result when verification fails
if (isVerificationErrors(insertDocumentResult)) {
expect(insertDocumentResult).toEqual({
message: 'Verification failed',
});
} else {
throw new Error('Result is not of type VerificationErrors');
}
// Assert - Behavior verification: Ensure create method was not called
expect(mockContainer.items.create).not.toHaveBeenCalled();
});
it('should insert document successfully', async () => {
// Arrange - create input and expected result data
const { input, result }: { input: RawInput; result: Partial<DbDocument> } =
createTestInputAndResult();
// Arrange - mock the input verification function to return true
(inputVerified as jest.Mock).mockReturnValue(true);
(mockContainer.items.create as jest.Mock).mockResolvedValue({
resource: result,
});
// Act - Call the function to test
const insertDocumentResult = await insertDocument(mockContainer, input);
// Assert - State verification: Check the result when insertion is successful
expect(insertDocumentResult).toEqual(result);
// Assert - Behavior verification: Ensure create method was called with correct arguments
expect(mockContainer.items.create).toHaveBeenCalledTimes(1);
expect(mockContainer.items.create).toHaveBeenCalledWith({
id: input.id,
name: result.name,
});
});
it('should return error if db insert fails', async () => {
// Arrange - create input and expected result data
const { input, result } = createTestInputAndResult();
// Arrange - mock the input verification function to return true
jest.mocked(inputVerified).mockReturnValue(true);
// Arrange - mock the Cosmos DB create method to throw an error
const mockError: DbError = {
message: 'An unknown error occurred',
code: 500,
};
jest.mocked(mockContainer.items.create).mockRejectedValue(mockError);
// Act - Call the function to test
const insertDocumentResult = await insertDocument(mockContainer, input);
// Assert - verify type as DbError
if (isDbError(insertDocumentResult)) {
expect(insertDocumentResult.message).toBe(mockError.message);
} else {
throw new Error('Result is not of type DbError');
}
// Assert - Behavior verification: Ensure create method was called with correct arguments
expect(mockContainer.items.create).toHaveBeenCalledTimes(1);
expect(mockContainer.items.create).toHaveBeenCalledWith({
id: input.id,
name: result.name,
});
});
});