Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
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 mocka SDK-anrop för molntjänster eller använda en live-tjänst för teständamål är det viktigt att överväga avvägningarna mellan hastighet, tillförlitlighet och kostnad. Den här artikeln visar hur du använder ett testramverk för att testa SDK-integrering. Programkoden infogar ett dokument i Cosmos DB. Testkoden hånar resursanvändningen så att molnresursen inte används.
De ramverk som används är:
- Jest med CommonJs
- Vitest med ESM
- Node.js Test runner med ESM
Förutsättningar
Node.js LTS. LTS-versionsstatus är "långsiktigt stöd", vilket vanligtvis garanterar att kritiska buggar kommer att åtgärdas i totalt 30 månader.
En Node.js testlöpare är en del av Node.js-installationen.
Försiktighet
Exemplet som tillhandahålls för Node.js-testköraren använder den experimentella nod:test-modulen med mock.fn(). Tänk på att Nodes inbyggda testkörare ännu inte erbjuder ett fullständigt stöd för mocking-API: et. Säkerställ att målversionen av Node stöder de experimentella API:erna, eller du kan överväga att använda ett tredjeparts bibliotek för mockning (eller stubfunktioner) istället.
Förlöjliga molntjänster
Fördelar:
- 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 integration.
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
Fördelar:
- Är en realistisk miljö som nära speglar produktionen?
- Är det användbart för integreringstester för att säkerställa att olika delar av systemet fungerar tillsammans?
- Är det bra 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 betraktas ofta som en dubbel när termen används i vardagligt tal. I den här artikeln används termen specifikt och illustreras specifikt i Jest-testramverket.
Simuleringar
Mockar (kallas även spioner): Ersätt 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.
import { mock } from 'node:test';
import assert from 'node:assert';
// ARRANGE
const dependencyFunctionMock = mock.fn();
// ACT
// Mock replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// ASSERT
assert.strictEqual(dependencyFunctionMock.mock.callCount(), 1);
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 mock-objekt 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.
import { describe, it, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
// ARRANGE
const fakeDatabaseData = {first: 'John', last: 'Jones'};
const dependencyFunctionMock = mock.fn();
dependencyFunctionMock.mock.mockImplementation((arg) => {
    return fakeDatabaseData;
});
// ACT
// Mock replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// ASSERT
assert.strictEqual(name, `${fakeDatabaseData.first} ${fakeDatabaseData.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
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
import assert from 'node:assert';
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);
}
describe('In-Mem DB', () => {
  let fakeDb: FakeDatabase;
  let testKey: string;
  let testValue: any;
  beforeEach(() => {
    fakeDb = new FakeDatabase();
    testKey = 'testKey';
    testValue = {
      first: 'John',
      last: 'Jones',
      lastUpdated: new Date().toISOString(),
    };
  });
  afterEach(() => {
    // Restore all mocks created by node:test’s mock helper.
    mock.restoreAll();
  });
  it('should save and return the correct value', () => {
    // Create a spy on the save method using node:test's mock helper.
    const saveSpy = mock.method(fakeDb, 'save').mock;
    // Call the function under test.
    const result = someTestFunction(fakeDb, testKey, testValue);
    // Verify state.
    assert.deepStrictEqual(result, testValue);
    assert.strictEqual(result.first, 'John');
    assert.strictEqual(result.last, 'Jones');
    assert.strictEqual(result.lastUpdated, testValue.lastUpdated);
    // Verify behavior
    assert.strictEqual(saveSpy.callCount(), 1);
    const calls = saveSpy.calls;
    assert.deepStrictEqual(calls[0].arguments, [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 omfattar denna funktionalitet:
// insertDocument.ts
import { Container } from '../data/connect-to-cosmos.js';
import type {
  DbDocument,
  DbError,
  RawInput,
  VerificationErrors,
} from '../data/model.js';
import Verify from '../data/verify.js';
export async function insertDocument(
  container: Container,
  doc: RawInput,
): Promise<DbDocument | DbError | VerificationErrors> {
  const isVerified: boolean = Verify.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;
    }
  }
}
Anmärkning
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:
| Funktion | 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-funktion för Azure Cosmos DB med @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
Det här ramverket tillhandahålls som en del av Node.js LTS.
Konfigurera paketet för att köra test
Uppdatera package.json för applikationen med ett nytt skript som testar våra källkodsfiler. Källkodsfiler definieras genom matchning på partiellt filnamn och filnamnstillägg. Testkörare 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 Test runner:
- * .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 Test runner:
"scripts": {
    "test": "node --test --experimental-test-coverage --experimental-test-module-mocks --trace-exit"
}
Konfigurera enhetstest för Azure SDK
Hur kan vi använda mocks, stubs och fakes för att testa funktionen insertDocument?
- Mock-objekt: vi behöver en mock för att säkerställa 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 visar funktionaliteten i din testfunktion, vilket kan verifieras. Ett exempel är att ett visst beroende anropades.
- Tillståndet anger de data som returneras från funktionen.
 
import { describe, it, afterEach, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
describe('boilerplate', () => {
  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 en extra konfiguration.
Det finns flera delar i testfilen:
- 
              import: Med importinstruktionerna kan du använda eller mocka vilket som helst av dina tester.
- 
              mock: Skapa det standardiserade simulerade beteende du vill använda. Varje test kan ändras efter behov.
- 
              describe: Testa gruppfamiljeninsert.tsför filen.
- 
              it: Varje test förinsert.tsfilen.
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. | 
| Felväg: should return error if db insert fails | Den simulerade databasmetoden anropades och returnerade ett fel. | 
Följande testfil visar hur du testar funktionen insertDocument .
// insertDocument.test.ts
import { describe, it, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
import { Container } from '../src/data/connect-to-cosmos.js';
import { createTestInputAndResult } from '../src/data/fake-data.js';
import type { DbDocument, DbError, RawInput } from '../src/data/model.js';
import { isDbError, isVerificationErrors } from '../src/data/model.js';
import Verify from '../src/data/verify.js';
import CosmosConnector from '../src/data/connect-to-cosmos.js';
import { insertDocument } from '../src/lib/insert.js';
describe('SDK', () => {
  beforeEach(() => {
    // Clear all mocks before each test
    mock.restoreAll();
  });
  it('should return verification error if input is not verified', async () => {
    const fakeContainer = {
      items: {
        create: async (_: any) => {
          throw new Error('Create method not implemented');
        },
      },
    } as unknown as Container;
    const mVerify = mock.method(Verify, 'inputVerified').mock;
    mVerify.mockImplementation(() => false);
    const mGetUniqueId = mock.method(CosmosConnector, 'getUniqueId').mock;
    mGetUniqueId.mockImplementation(() => 'unique-id');
    const mContainerCreate = mock.method(fakeContainer.items, 'create').mock;
    // Arrange: wrong shape of document on purpose.
    const doc = { name: 'test' } as unknown as RawInput;
    // Act:
    const insertDocumentResult = await insertDocument(fakeContainer, doc);
    // Assert - State verification.
    if (isVerificationErrors(insertDocumentResult)) {
      assert.deepStrictEqual(insertDocumentResult, {
        message: 'Verification failed',
      });
    } else {
      throw new Error('Result is not of type VerificationErrors');
    }
    // Assert - Behavior verification: Verify that create was never called.
    assert.strictEqual(mContainerCreate.callCount(), 0);
  });
  it('should insert document successfully', async () => {
    // Arrange: override inputVerified to return true.
    const { input, result }: { input: RawInput; result: Partial<DbDocument> } =
      createTestInputAndResult();
    const fakeContainer = {
      items: {
        create: async (doc: any) => {
          return { resource: result };
        },
      },
    } as unknown as Container;
    const mVerify = mock.method(Verify, 'inputVerified').mock;
    mVerify.mockImplementation(() => true);
    const mContainerCreate = mock.method(
      fakeContainer.items as any,
      'create',
    ).mock;
    mContainerCreate.mockImplementation(async (doc: any) => {
      return { resource: result };
    });
    // Act:
    const receivedResult = await insertDocument(fakeContainer, input);
    // Assert - State verification: Ensure the result is as expected.
    assert.deepStrictEqual(receivedResult, result);
    // Assert - Behavior verification: Ensure create was called once with correct arguments.
    assert.strictEqual(mContainerCreate.callCount(), 1);
    assert.deepStrictEqual(mContainerCreate.calls[0].arguments[0], {
      id: input.id,
      name: result.name,
    });
  });
  it('should return error if db insert fails', async () => {
    // Arrange: override inputVerified to return true.
    const { input, result } = createTestInputAndResult();
    const errorMessage: string = 'An unknown error occurred';
    const fakeContainer = {
      items: {
        create: async (doc: any): Promise<any> => {
          return Promise.resolve(null);
        },
      },
    } as unknown as Container;
    const mVerify = mock.method(Verify, 'inputVerified').mock;
    mVerify.mockImplementation(() => true);
    const mContainerCreate = mock.method(fakeContainer.items, 'create').mock;
    mContainerCreate.mockImplementation(async (doc: any) => {
      const mockError: DbError = {
        message: errorMessage,
        code: 500,
      };
      throw mockError;
    });
    // Act:
    const insertDocumentResult = await insertDocument(fakeContainer, input);
    // // Assert - Ensure create method was called once with the correct arguments.
    assert.strictEqual(isDbError(insertDocumentResult), true);
    assert.strictEqual(mContainerCreate.callCount(), 1);
    assert.deepStrictEqual(mContainerCreate.calls[0].arguments[0], {
      id: input.id,
      name: result.name,
    });
  });
});
Felsökning
Merparten av koden i den här artikeln kommer från GitHub-lagringsplatsen MicrosoftDocs/node-essentials . Om du vill infoga i en Cosmos DB Cloud-resurs skapar du resursen med det här skriptet.