Typsäkra API:er från frontend till backend med tRPC · Lektion

Integrationstestning av tRPC-routers

Konfigurera integrationstester för att verifiera att olika tRPC-procedurer och deras beroenden samverkar korrekt.

Lektion 2 av 411 steg

Integrationstestning av tRPC-routers är en gratis lektion i Typsäkra API:er från frontend till backend med tRPC på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Typsäkra API:er från frontend till backend med tRPC, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Typsäkra API:er från frontend till backend med tRPC innehåller totalt 4 lektioner.

Vad är integrationstester?

Välkommen till integrationstestning för tRPC! Medan enhetstester kontrollerar enskilda funktioner verifierar integrationstester hur olika delar av din tRPC-applikation fungerar tillsammans.

Det innebär ofta att testa samspelet mellan tRPC-procedurer och externa beroenden, till exempel en databas eller en annan tjänst.

Enhets- jämfört med integrationstester

Vi tydliggör skillnaden:

  • Enhetstester: Fokuserar på att isolera en enda tRPC-procedur. Alla externa beroenden mockas för att testa enbart procedurens logik.
  • Integrationstester: Testar flödet mellan flera procedurer eller mellan en procedur och ett verkligt (eller mockat) externt beroende, till exempel att en mutation uppdaterar en databas korrekt.

Förberedelser för integrationstester

För integrationstester behöver du en kontrollerad miljö. Det innebär ofta att:

  • Använda en databas i minnet eller en särskild testdatabasinstans.
  • Mocka endast nödvändiga externa tjänster, inte centrala beroenden som databasen.
  • Konfigurera en tRPC-testanropare för att anropa procedurer direkt och kringgå HTTP-anrop.

Skapa en tRPC-testanropare

För att anropa dina tRPC-procedurer direkt i tester skapar du en testanropare. Med detta objekt kan du interagera med din router utan en fullständig HTTP-server, vilket gör testerna snabbare och enklare att konfigurera.

Prova att köra det här exemplet för att se en grundläggande anropare i praktiken:

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();

Testa en frågeprocedur

Integrationstester för frågor verifierar att data hämtas korrekt, ofta med hjälp av en mockad databas. Vi säkerställer att frågan returnerar förväntade data baserat på det aktuella tillståndet.

Observera hur greeting-frågan fungerar med och utan indata:

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();

Testa en mutationsprocedur

Mutationer ändrar data. Ett integrationstest för en mutation bör verifiera att data ändras som förväntat och att efterföljande frågor återspeglar dessa ändringar. Vi simulerar en databas 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();

Verifiera sammanslagna routrar

I större tRPC-applikationer organiserar du API:et i flera routrar och slår sedan samman dem till en rotrouter. Integrationstester bör bekräfta att procedurer från olika sammanslagna routrar kan nås och interagerar korrekt.

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();

Testa med kontextdata

Många tRPC-procedurer är beroende av objektet context, som kan innehålla data såsom en autentiserad användares ID eller roller. I integrationstester anger du en mockad kontext när du skapar din anropare för att simulera olika användartillstånd.

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();

Rensa testdata

Integrationstester som ändrar delat tillstånd (till exempel en databas) kan skapa beroenden mellan tester om de inte hanteras noggrant. Det är viktigt att rensa alla skapade data efter varje test eller testsvit.

  • Använd krokarna beforeEach/afterEach i ditt testkörningsverktyg.
  • Återställ mockade databaser eller töm tabeller för att säkerställa att testerna är isolerade och kan upprepas.

Utmaning: Integrationstestning

Föreställ dig en tRPC-konfiguration där en userRouter hanterar skapande av användare och en postRouter hanterar skapande av inlägg, där inlägg kräver ett giltigt userId. Du skriver ett integrationstest.

Sammanfattning: Integrationstestning av tRPC

Vi har gått igenom hur man utför integrationstester för tRPC-routrar. Ni har lärt er att:

  • Använda en testanropare för att anropa procedurer direkt.
  • Testa både query- och mutation-procedurer och observera tillståndsändringar.
  • Verifiera interaktioner mellan sammanslagna routrar.
  • Tillhandahålla en simulerad kontext för autentiserade tester.
  • Förstå vikten av att städa upp testdata.

Integrationstester är avgörande för att säkerställa att komponenterna i ert tRPC-API fungerar harmoniskt och interagerar korrekt med sina beroenden.

Gratis att börja

Lär dig Typsäkra API:er från frontend till backend med tRPC med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
10
Lektioner
40

Vanliga frågor

Är lektionen ”Integrationstestning av tRPC-routers” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Typsäkra API:er från frontend till backend med tRPC, inklusive ”Integrationstestning av tRPC-routers”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Typsäkra API:er från frontend till backend med tRPC innehåller totalt 4 lektioner.

Vad lär jag mig i ”Integrationstestning av tRPC-routers”?

Konfigurera integrationstester för att verifiera att olika tRPC-procedurer och deras beroenden samverkar korrekt. Ni övar på Typsäkra API:er från frontend till backend med tRPC med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Typsäkra API:er från frontend till backend med tRPC?

Du behöver inga förkunskaper. Utbildningen i Typsäkra API:er från frontend till backend med tRPC på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”Integrationstestning av tRPC-routers”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Typsäkra API:er från frontend till backend med tRPC-lektionen?

Ja. Varje Typsäkra API:er från frontend till backend med tRPC-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Enhetstestning av tRPC-procedurer
  2. Integrationstestning av tRPC-routers
  3. Strategier för end-to-end-testning
  4. Mocka kontext och beroenden i tRPC-tester
← Tillbaka till Typsäkra API:er från frontend till backend med tRPC