Typesikre API-er fra frontend til backend med tRPC · leksjon

Integrasjonstesting av tRPC-rutere

Sett opp integrasjonstester for å bekrefte korrekt samhandling mellom ulike tRPC-prosedyrer og avhengighetene deres.

Leksjon 2 av 411 trinn

Integrasjonstesting av tRPC-rutere er en gratis leksjon i Typesikre API-er fra frontend til backend med tRPC på CoddyKit. Dette er leksjon 2 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Typesikre API-er fra frontend til backend med tRPC, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Typesikre API-er fra frontend til backend med tRPC inneholder totalt 4 leksjoner.

Hva er integrasjonstester?

Velkommen til integrasjonstesting for tRPC! Mens enhetstester kontrollerer individuelle funksjoner, verifiserer integrasjonstester hvordan ulike deler av tRPC-applikasjonen fungerer sammen.

Dette innebærer ofte å teste samhandlingen mellom tRPC-prosedyrer og eksterne avhengigheter, for eksempel en database eller en annen tjeneste.

Enhetstester kontra integrasjonstester

La oss klargjøre forskjellen:

  • Enhetstester: Fokuserer på å isolere én enkelt tRPC-prosedyre. De mocker alle eksterne avhengigheter for å teste prosedyrelogikken alene.
  • Integrasjonstester: Tester flyten mellom flere prosedyrer eller mellom en prosedyre og en ekte (eller mocket) ekstern avhengighet, for eksempel at en mutasjon oppdaterer en database riktig.

Klargjøring for integrasjonstester

For integrasjonstester trenger du et kontrollert miljø. Det innebærer ofte:

  • Å bruke en database i minnet eller en egen testdatabaseinstans.
  • Å mocke bare nødvendige eksterne tjenester, ikke kjerneavhengigheter som databasen.
  • Å sette opp en tRPC-test-caller for å kalle prosedyrer direkte og omgå HTTP-forespørsler.

Opprette en tRPC-test-caller

For å kalle tRPC-prosedyrer direkte i tester oppretter du en test-caller. Dette objektet lar deg samhandle med routeren uten en full HTTP-server, slik at testene blir raskere og enklere å sette opp.

Prøv å kjøre dette eksempelet for å se en grunnleggende caller i praksis:

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<any>().create();
const publicProcedure = t.procedure;

const appRouter = t.router({
  greeting: publicProcedure
    .input(z.object({ name: z.string() }).optional())
    .query(({ input }) => {
      return `Hello, ${input?.name || 'world'}!`;
    }),
  addNumber: publicProcedure
    .input(z.object({ num1: z.number(), num2: z.number() }))
    .mutation(({ input }) => {
      return input.num1 + input.num2;
    }),
});

const caller = appRouter.createCaller({});

async function runSimulatedTest() {
  console.log("Simulating tRPC integration test...");

  const greetingResult = await caller.greeting.query({ name: "Coddy" });
  console.log("Greeting result:", greetingResult);

  const sumResult = await caller.addNumber.mutation({ num1: 5, num2: 3 });
  console.log("Sum result:", sumResult);
}

runSimulatedTest();

Teste en spørringsprosedyre

Integrasjonstester for spørringer verifiserer at data hentes riktig, ofte ved hjelp av en mock-database. Vi sørger for at spørringen returnerer forventede data basert på den nåværende tilstanden.

Se hvordan greeting-spørringen oppfører seg med og uten input:

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<any>().create();
const publicProcedure = t.procedure;

const appRouter = t.router({
  greeting: publicProcedure
    .input(z.object({ name: z.string() }).optional())
    .query(({ input }) => {
      // In a real test, this might fetch from a DB
      return `Hello, ${input?.name || 'world'}!`;
    }),
});

const caller = appRouter.createCaller({});

async function testGreetingQuery() {
  console.log("--- Testing 'greeting' query ---");

  // Test with a name
  const resultWithName = await caller.greeting.query({ name: "Alice" });
  console.log("Result with name:", resultWithName);

  // Test without a name (default behavior)
  const resultNoName = await caller.greeting.query();
  console.log("Result no name:", resultNoName);

  console.log("Query tests complete.");
}

testGreetingQuery();

Teste en mutasjonsprosedyre

Mutasjoner endrer data. En integrasjonstest for en mutasjon bør kontrollere at dataene endres som forventet, og at etterfølgende spørringer gjenspeiler disse endringene. Vi simulerer en database i minnet.

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<any>().create();
const publicProcedure = t.procedure;

// Simulate an in-memory database
const _db: { messages: string[] } = { messages: [] };

const appRouter = t.router({
  getMessages: publicProcedure
    .query(() => _db.messages),
  addMessage: publicProcedure
    .input(z.object({ text: z.string() }))
    .mutation(({ input }) => {
      _db.messages.push(input.text);
      return input.text;
    }),
});

const caller = appRouter.createCaller({});

async function testAddMessageMutation() {
  console.log("--- Testing 'addMessage' mutation ---");

  // 1. Check initial state
  let initialMessages = await caller.getMessages.query();
  console.log("Initial messages:", initialMessages);

  // 2. Perform mutation
  const addedText = await caller.addMessage.mutation({ text: "Hello tRPC!" });
  console.log("Added message:", addedText);

  // 3. Check state after mutation
  let updatedMessages = await caller.getMessages.query();
  console.log("Updated messages:", updatedMessages);

  // 4. Perform another mutation
  await caller.addMessage.mutation({ text: "More data!" });
  updatedMessages = await caller.getMessages.query();
  console.log("Further updated messages:", updatedMessages);

  console.log("Mutation tests complete.");
}

testAddMessageMutation();

Verifisere sammenslåtte routere

I større tRPC-applikasjoner organiserer du API-et i flere routere og slår dem deretter sammen til en rot-router. Integrasjonstester bør bekrefte at prosedyrer fra ulike sammenslåtte routere kan nås og samhandle riktig.

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<any>().create();
const publicProcedure = t.procedure;

// Simulate an in-memory database
const _db: { users: string[]; products: string[] } = { users: [], products: [] };

// User router
const userRouter = t.router({
  getUsers: publicProcedure.query(() => _db.users),
  addUser: publicProcedure
    .input(z.object({ name: z.string() }))
    .mutation(({ input }) => {
      _db.users.push(input.name);
      return input.name;
    }),
});

// Product router
const productRouter = t.router({
  getProducts: publicProcedure.query(() => _db.products),
  addProduct: publicProcedure
    .input(z.object({ name: z.string() }))
    .mutation(({ input }) => {
      _db.products.push(input.name);
      return input.name;
    }),
});

// Root router merging user and product routers
const appRouter = t.router({
  user: userRouter,
  product: productRouter,
});

const caller = appRouter.createCaller({});

async function testMergedRouters() {
  console.log("--- Testing merged routers ---");

  // Add a user
  await caller.user.addUser.mutation({ name: "Bob" });
  const users = await caller.user.getUsers.query();
  console.log("Users after adding Bob:", users);

  // Add a product
  await caller.product.addProduct.mutation({ name: "Laptop" });
  const products = await caller.product.getProducts.query();
  console.log("Products after adding Laptop:", products);

  console.log("All users:", await caller.user.getUsers.query());
  console.log("All products:", await caller.product.getProducts.query());

  console.log("Merged router tests complete.");
}

testMergedRouters();

Testing med kontekstdata

Mange tRPC-prosedyrer er avhengige av kontekst-objektet, som kan inneholde data som ID-en eller rollene til en autentisert bruker. I integrasjonstester oppgir du en mock-kontekst når du oppretter calleren, slik at du kan simulere ulike brukertilstander.

import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';

// Define the shape of your context
interface MyContext {
  userId?: string;
  isAdmin?: boolean;
}

const t = initTRPC.context<MyContext>().create();
const publicProcedure = t.procedure;

const protectedProcedure = publicProcedure.use(
  t.middleware(({ ctx, next }) => {
    if (!ctx.userId) {
      throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' });
    }
    return next({ ctx: { ...ctx, userId: ctx.userId } });
  })
);

const adminProcedure = protectedProcedure.use(
  t.middleware(({ ctx, next }) => {
    if (!ctx.isAdmin) {
      throw new TRPCError({ code: 'FORBIDDEN', message: 'Not an admin' });
    }
    return next({ ctx: { ...ctx, isAdmin: ctx.isAdmin } });
  })
);

const appRouter = t.router({
  whoAmI: protectedProcedure.query(({ ctx }) => `You are user ${ctx.userId}`),
  adminAction: adminProcedure.mutation(() => "Admin action successful!"),
});

async function testWithMockContext() {
  console.log("--- Testing with mock context ---");

  // 1. Test unauthenticated user
  const unauthCaller = appRouter.createCaller({});
  try {
    await unauthCaller.whoAmI.query();
  } catch (error: any) {
    console.log("Unauthenticated user error:", error.message);
  }

  // 2. Test authenticated user
  const authCaller = appRouter.createCaller({ userId: "user123" });
  const userResult = await authCaller.whoAmI.query();
  console.log("Authenticated user result:", userResult);

  // 3. Test non-admin user trying admin action
  try {
    await authCaller.adminAction.mutation();
  } catch (error: any) {
    console.log("Non-admin user error:", error.message);
  }

  // 4. Test admin user
  const adminCaller = appRouter.createCaller({ userId: "admin456", isAdmin: true });
  const adminResult = await adminCaller.adminAction.mutation();
  console.log("Admin user result:", adminResult);

  console.log("Context tests complete.");
}

testWithMockContext();

Rydde opp i testdata

Integrasjonstester som endrer delt tilstand, for eksempel en database, kan skape avhengigheter mellom testene hvis de ikke håndteres nøye. Det er avgjørende å rydde opp i alle opprettede data etter hver test eller testpakke.

  • Bruk beforeEach/afterEach-hooks i testkjøreren.
  • Tilbakestill mock-databaser eller tøm tabeller for å sikre at testene er isolerte og kan gjentas.

Utfordring: integrasjonstesting

Tenk deg et tRPC-oppsett der en userRouter håndterer opprettelse av brukere, og en postRouter håndterer opprettelse av innlegg, der innlegg krever en gyldig userId. Du skriver en integrasjonstest.

Oppsummering: integrasjonstesting av tRPC

Vi har utforsket hvordan du utfører integrasjonstester for tRPC-rutere. Du har lært å:

  • Bruke en testkaller til å kalle prosedyrer direkte.
  • Teste både spørrings- og mutasjonsprosedyrer og observere tilstands endringer.
  • Bekrefte samhandling på tvers av sammenslåtte rutere.
  • Oppgi en simulert kontekst for autentiserte tester.
  • Forstå viktigheten av opprydding i testdata.

Integrasjonstester er avgjørende for å sikre at komponentene i tRPC-API-et fungerer harmonisk sammen og samhandler korrekt med avhengighetene sine.

Gratis å komme i gang

Lær deg Typesikre API-er fra frontend til backend med tRPC med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
10
Leksjoner
40

Ofte stilte spørsmål

Er leksjonen «Integrasjonstesting av tRPC-rutere» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Typesikre API-er fra frontend til backend med tRPC, inkludert «Integrasjonstesting av tRPC-rutere», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Typesikre API-er fra frontend til backend med tRPC inneholder totalt 4 leksjoner.

Hva lærer jeg i «Integrasjonstesting av tRPC-rutere»?

Sett opp integrasjonstester for å bekrefte korrekt samhandling mellom ulike tRPC-prosedyrer og avhengighetene deres. Du øver på Typesikre API-er fra frontend til backend med tRPC med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Typesikre API-er fra frontend til backend med tRPC?

Ingen tidligere erfaring er nødvendig. Typesikre API-er fra frontend til backend med tRPC på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Integrasjonstesting av tRPC-rutere»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Typesikre API-er fra frontend til backend med tRPC-leksjonen?

Ja. Alle Typesikre API-er fra frontend til backend med tRPC-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Enhetstesting av tRPC-prosedyrer
  2. Integrasjonstesting av tRPC-rutere
  3. Strategier for ende-til-ende-testing
  4. Mocking av kontekst og avhengigheter i tRPC-tester
← Tilbake til Typesikre API-er fra frontend til backend med tRPC