Testa Azure SDK-integrering i JavaScript-program med Jest
Att testa din integreringskod för Azure SDK för JavaScript är viktigt för att säkerställa att dina program interagerar korrekt med Azure-tjänster. Den här guiden visar hur du effektivt testar Azure SDK-integrering i dina JavaScript-program som ett testramverk.
När du bestämmer dig för att håna molntjänst-SDK-anrop eller använda en livetjänst i testsyfte är det viktigt att överväga kompromisserna mellan hastighet, tillförlitlighet och kostnad. Den här artikeln visar hur du använder Jest som testramverk för att testa SDK-integrering. Andra jämförbara testramverk kan också användas.
Förutsättningar
Håna molntjänster
Proffsen:
- Påskyndar testpaketet genom att eliminera nätverksfördröjning.
- Tillhandahåller förutsägbara och kontrollerade testmiljöer.
- Enklare att simulera olika scenarier och gränsfall.
- Minskar kostnaderna för användning av livemolntjänster, särskilt i pipelines för kontinuerlig integrering.
Nackdelar:
- Mockobjekt kan glida från det faktiska SDK:et, vilket leder till avvikelser.
- Kan ignorera vissa funktioner eller beteenden i livetjänsten.
- Mindre realistisk miljö jämfört med produktion.
Använda en livetjänst
Proffsen:
- Är en realistisk miljö som nära speglar produktionen.
- Är användbart för integreringstester för att säkerställa att olika delar av systemet fungerar tillsammans.
- Är användbart för att identifiera problem som rör nätverkstillförlitlighet, tjänsttillgänglighet och faktisk datahantering.
Nackdelar:
- Är långsammare på grund av nätverksanrop.
- Är dyrare på grund av potentiella kostnader för tjänstanvändning.
- Är komplext och tidskrävande att konfigurera och underhålla en livetjänstmiljö som matchar produktionen.
Valet mellan att håna och använda livetjänster beror på din teststrategi. För enhetstester där hastighet och kontroll är av största vikt är hån ofta det bättre valet. För integreringstester där realism är avgörande kan användning av en livetjänst ge mer exakta resultat. Genom att balansera dessa metoder kan du uppnå omfattande testtäckning samtidigt som du hanterar kostnader och bibehåller testeffektiviteten.
Testdubblar: Mocks, stubs och fakes
En testdubblett är någon form av ersättning som används i stället för något verkligt i testsyfte. Vilken typ av dubbel du väljer baseras på vad du vill att den ska ersätta. Termen mock är ofta menad som en dubbel när termen används tillfälligt. I den här artikeln används termen specifikt och illustreras specifikt i Jest-testramverket.
Simuleringar
Hånar (kallas även spioner): Ersätt i en funktion och kunna kontrollera och spionera på funktionens beteende när den indirekt anropas av någon annan kod.
I följande exempel har du två funktioner:
-
someTestFunction: Funktionen du behöver testa. Det anropar ett beroende,
dependencyFunction
, som du inte skrev och inte behöver testa. - dependencyFunctionMock: Mockning av beroendet.
// setup
const dependencyFunctionMock = jest.fn();
// perform test
// Jest replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// verify behavior
expect(dependencyFunctionMock).toHaveBeenCalled();
Syftet med testet är att säkerställa att vissaTestFunction fungerar korrekt utan att faktiskt anropa beroendekoden. Testet verifierar att modellen för beroendet anropades.
Simulera stora eller små beroenden
När du bestämmer dig för att håna ett beroende kan du välja att håna precis det du behöver, till exempel:
- En funktion eller två från ett större beroende. Jest erbjuder partiella hån för detta ändamål.
- Alla funktioner i ett mindre beroende, som du ser i exemplet i den här artikeln.
Stub-funktioner
Syftet med en stub är att ersätta en funktions returdata för att simulera olika scenarier. Du använder en stub för att tillåta att koden anropar funktionen och tar emot olika tillstånd, inklusive lyckade resultat, fel, undantag och gränsfall. Tillståndsverifiering säkerställer att koden hanterar dessa scenarier korrekt.
// 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}`);
Syftet med föregående test är att säkerställa att det arbete som utförs av someTestFunction
uppfyller det förväntade resultatet. I det här enkla exemplet är funktionens uppgift att sammanfoga för- och efternamnen. Genom att använda falska data känner du till det förväntade resultatet och kan verifiera att funktionen utför arbetet korrekt.
Förfalskningar
Förfalskningar ersätter en funktion som du normalt inte skulle använda i produktion, till exempel att använda en minnesintern databas i stället för en molndatabas.
// 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);
});
});
Syftet med föregående test är att säkerställa att someTestFunction
det interagerar korrekt med databasen. Genom att använda en falsk minnesintern databas kan du testa funktionens logik utan att förlita dig på en riktig databas, vilket gör testerna snabbare och mer tillförlitliga.
Scenario: Infoga dokument i Cosmos DB med Hjälp av Azure SDK
Anta att du har ett program som behöver skriva ett nytt dokument till Cosmos DB om all information skickas och verifieras. Om ett tomt formulär skickas eller om informationen inte matchar det förväntade formatet ska programmet inte ange data.
Cosmos DB används som exempel, men begreppen gäller för de flesta Azure SDK:er för JavaScript. Följande funktion samlar in den här funktionen:
// 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;
}
}
}
Kommentar
TypeScript-typer hjälper dig att definiera vilka typer av data som en funktion använder. Även om du inte behöver TypeScript för att använda Jest eller andra JavaScript-testramverk är det viktigt för att skriva typsäkra JavaScript.
Funktionerna i det här programmet är:
Function | beskrivning |
---|---|
insertDocument | Infogar ett dokument i databasen. Det här är vad vi vill testa. |
inputVerified | Verifierar indata mot ett schema. Säkerställer att data har rätt format (till exempel giltiga e-postadresser, korrekt formaterade URL:er). |
cosmos.items.create | SDK-funktionen för Azure Cosmos DB med hjälp av @azure/cosmos. Det här är vad vi vill håna. Det har redan egna tester som underhålls av paketägarna. Vi måste kontrollera att Cosmos DB-funktionsanropet gjordes och returnerade data om inkommande data passerade verifieringen. |
Installera testramverksberoende
I den här artikeln används Jest som testramverk. Det finns andra testramverk som är jämförbara som du också kan använda.
I roten i programkatalogen installerar du Jest med följande kommando:
npm install jest
Konfigurera paketet för att köra test
package.json
Uppdatera för programmet med ett nytt skript för att testa våra källkodsfiler. Källkodsfiler definieras genom matchning på partiellt filnamn och filnamnstillägg. Jest söker efter filer som följer den vanliga namngivningskonventionen för testfiler: <file-name>.spec.[jt]s
. Det här mönstret innebär att filer som heter som följande exempel tolkas som testfiler och körs av Jest:
- * .test.js: Till exempel math.test.js
- * .spec.js: Till exempel math.spec.js
- Filer som finns i en testkatalog, till exempel tester/math.js
Lägg till ett skript i package.json för att stödja testfilmönstret med Jest:
"scripts": {
"test": "jest dist",
}
TypeScript-källkoden genereras i undermappen dist
. Jest kör filerna .spec.js
som finns i undermappen dist
.
Konfigurera enhetstest för Azure SDK
Hur kan vi använda mocks, stubs och förfalskningar för att testa funktionen insertDocument ?
- Modeller: Vi behöver ett hån för att se till att funktionens beteende testas, till exempel:
- Om data klarar verifieringen skedde anropet till Cosmos DB-funktionen bara 1 gång
- Om data inte klarar verifieringen skedde inte anropet till Cosmos DB-funktionen
- Män:
- De data som skickas in matchar det nya dokument som returneras av funktionen.
När du testar bör du tänka på testkonfigurationen, själva testet och verifieringen. När det gäller test vernacular använder den här funktionen följande termer:
- Ordna: Konfigurera dina testvillkor
- Agera: anropa din funktion för att testa, även kallat systemet under test eller SUT
- Bekräfta: verifiera resultatet. Resultatet kan vara beteende eller tillstånd.
- Beteendet anger funktioner i testfunktionen, som kan verifieras. Ett exempel är att ett visst beroende anropades.
- Tillståndet anger de data som returneras från funktionen.
Jest, precis som andra testramverk, använder en mall för testfiler.
// 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
});
});
När du använder mocks i dina tester måste mallkoden använda mocking för att testa funktionen utan att anropa det underliggande beroendet som används i funktionen, till exempel Azure-klientbiblioteken.
Skapa testfilen
Testfilen med mocks, för att simulera ett anrop till ett beroende, har extra konfiguration. Det finns flera delar i testfilen:
-
import
: Med importinstruktionerna kan du använda eller håna något av dina test. -
jest.mock
: Skapa standardbeteendet som du vill använda. Varje test kan ändras efter behov. -
describe
: Testa gruppfamiljeninsert.ts
för filen. -
test
: Varje test förinsert.ts
filen.
Testfilen omfattar tre tester för insert.ts
filen, som kan delas in i två valideringstyper:
Valideringstyp | Test |
---|---|
Lycklig väg: should insert document successfully |
Den simulerade databasmetoden anropades och returnerade ändrade data. |
Felsökväg: should return verification error if input is not verified |
Data kunde inte verifieras och ett fel returnerades. |
Felsökväg:should return error if db insert fails |
Den simulerade databasmetoden anropades och returnerade ett fel. |
Följande Jest-testfil visar hur du testar funktionen 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,
});
});
});
Ytterligare information
- Bästa praxis för Jest-hån
- Skillnaden mellan Mocks och Stubs av Martin Fowler