Advanced API testing patterns and techniques for production-grade test suites.
This lesson covers grpc api testing with k6 and grpcurl, an essential skill for building robust API test suites that catch regressions before they reach production. You will learn the patterns and tools needed to test complex API behavior systematically.
Setting up the testing environment for gRPC API Testing with k6 and grpcurl:
1// Install required dependencies
2// npm install --save-dev supertest @types/supertest
3
4import { Test, TestingModule } from '@nestjs/testing';
5import { INestApplication, ValidationPipe } from '@nestjs/common';
6import * as request from 'supertest';
7import { AppModule } from '../../app.module';
8
9describe('gRPC API Testing with k6 and grpcurl', () => {
10 let app: INestApplication;
11
12 beforeAll(async () => {
13 const module: TestingModule = await Test.createTestingModule({
14 imports: [AppModule],
15 }).compile();
16 app = module.createNestApplication();
17 app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
18 await app.init();
19 });
20
21 afterAll(() => app.close());
22
23 it('basic endpoint health check', async () => {
24 const res = await request(app.getHttpServer()).get('/api/health');
25 expect(res.status).toBe(200);
26 });
27});Key testing patterns for gRPC API Testing with k6 and grpcurl:
1// Pattern 1: Arrange-Act-Assert
2describe('API endpoint behavior', () => {
3 it('returns correct response for valid request', async () => {
4 // Arrange
5 const token = await getAuthToken();
6 const payload = { name: 'Test Resource', value: 42 };
7
8 // Act
9 const res = await request(app.getHttpServer())
10 .post('/api/resources')
11 .set('Authorization', `Bearer ${token}`)
12 .send(payload);
13
14 // Assert
15 expect(res.status).toBe(201);
16 expect(res.body).toMatchObject({
17 id: expect.any(String),
18 name: payload.name,
19 value: payload.value,
20 });
21 });
22
23 it('handles concurrent requests correctly', async () => {
24 const requests = Array.from({ length: 10 }, () =>
25 request(app.getHttpServer()).get('/api/resources')
26 );
27 const results = await Promise.all(requests);
28 results.forEach(res => expect(res.status).toBe(200));
29 });
30});Testing error responses and edge cases:
1describe('Error handling', () => {
2 it('returns 400 for invalid input', async () => {
3 const res = await request(app.getHttpServer())
4 .post('/api/resources')
5 .send({ invalidField: 'value' });
6 expect(res.status).toBe(400);
7 expect(res.body.message).toBeDefined();
8 });
9
10 it('returns 401 without auth token', async () => {
11 const res = await request(app.getHttpServer()).post('/api/resources');
12 expect(res.status).toBe(401);
13 });
14
15 it('returns 404 for non-existent resource', async () => {
16 const res = await request(app.getHttpServer()).get('/api/resources/non-existent');
17 expect(res.status).toBe(404);
18 });
19
20 it('handles large page sizes gracefully', async () => {
21 const res = await request(app.getHttpServer()).get('/api/resources?limit=99999');
22 expect([200, 400]).toContain(res.status);
23 if (res.status === 200) {
24 expect(res.body.data.length).toBeLessThanOrEqual(100);
25 }
26 });
27});Integrating API tests into CI/CD pipelines:
1# .github/workflows/api-tests.yml
2name: API Integration Tests
3on: [push, pull_request]
4jobs:
5 api-tests:
6 runs-on: ubuntu-latest
7 services:
8 postgres:
9 image: postgres:16
10 env:
11 POSTGRES_PASSWORD: postgres
12 POSTGRES_DB: app_test
13 options: >-
14 --health-cmd pg_isready
15 --health-interval 10s
16 --health-timeout 5s
17 --health-retries 5
18 steps:
19 - uses: actions/checkout@v4
20 - uses: actions/setup-node@v4
21 with: { node-version: '20', cache: 'npm' }
22 - run: npm ci
23 - run: npx prisma migrate deploy
24 env:
25 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/app_test
26 - run: npm run test:api
27 env:
28 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/app_test
29 JWT_SECRET: test-secret-key-32-chars-minimum
30 - uses: actions/upload-artifact@v4
31 if: always()
32 with:
33 name: api-test-results
34 path: test-results/Advanced techniques for maintainable API test suites:
1// Shared test helpers for consistent setup
2export class ApiTestHelper {
3 constructor(private readonly app: INestApplication) {}
4
5 async authenticate(email: string, password: string): Promise<string> {
6 const res = await request(this.app.getHttpServer())
7 .post('/auth/login')
8 .send({ email, password });
9 if (res.status !== 200) throw new Error('Auth failed: ' + res.status);
10 return res.body.accessToken;
11 }
12
13 async createResource(token: string, data: Record<string, unknown>) {
14 const res = await request(this.app.getHttpServer())
15 .post('/api/resources')
16 .set('Authorization', `Bearer ${token}`)
17 .send(data);
18 expect(res.status).toBe(201);
19 return res.body;
20 }
21
22 async expectPaginatedResponse(path: string, token?: string) {
23 const req = request(this.app.getHttpServer()).get(path);
24 if (token) req.set('Authorization', `Bearer ${token}`);
25 const res = await req;
26 expect(res.status).toBe(200);
27 expect(res.body).toMatchObject({
28 data: expect.any(Array),
29 meta: expect.objectContaining({ total: expect.any(Number) }),
30 });
31 return res.body;
32 }
33}A comprehensive example demonstrating grpc api testing with k6 and grpcurl with real-world patterns.
1// Complete integration test for gRPC API Testing with k6 and grpcurl
2import { Test, TestingModule } from '@nestjs/testing';
3import { INestApplication, ValidationPipe } from '@nestjs/common';
4import * as request from 'supertest';
5import { AppModule } from '../../app.module';
6import { PrismaService } from '../prisma/prisma.service';
7
8describe('gRPC API Testing with k6 and grpcurl', () => {
9 let app: INestApplication;
10 let prisma: PrismaService;
11 let authToken: string;
12
13 const TEST_RUN = 'test-' + Date.now();
14
15 beforeAll(async () => {
16 const module: TestingModule = await Test.createTestingModule({
17 imports: [AppModule],
18 }).compile();
19
20 app = module.createNestApplication();
21 app.useGlobalPipes(new ValidationPipe({
22 whitelist: true,
23 forbidNonWhitelisted: true,
24 transform: true,
25 }));
26 await app.init();
27 prisma = module.get(PrismaService);
28
29 const loginRes = await request(app.getHttpServer())
30 .post('/auth/login')
31 .send({ email: '[email protected]', password: 'AdminPass123!' });
32 authToken = loginRes.body.accessToken;
33 });
34
35 afterAll(async () => {
36 // Clean up test-specific data
37 await prisma.course.deleteMany({ where: { slug: { startsWith: TEST_RUN } } });
38 await app.close();
39 });
40
41 describe('Happy path scenarios', () => {
42 it('completes the primary workflow successfully', async () => {
43 // Step 1: Create resource
44 const createRes = await request(app.getHttpServer())
45 .post('/api/resources')
46 .set('Authorization', 'Bearer ' + authToken)
47 .send({ name: TEST_RUN + '-resource', value: 42 });
48 expect(createRes.status).toBe(201);
49 const resourceId = createRes.body.id;
50
51 // Step 2: Retrieve and verify
52 const getRes = await request(app.getHttpServer())
53 .get('/api/resources/' + resourceId)
54 .set('Authorization', 'Bearer ' + authToken);
55 expect(getRes.status).toBe(200);
56 expect(getRes.body.id).toBe(resourceId);
57
58 // Step 3: Update
59 const updateRes = await request(app.getHttpServer())
60 .patch('/api/resources/' + resourceId)
61 .set('Authorization', 'Bearer ' + authToken)
62 .send({ value: 99 });
63 expect(updateRes.status).toBe(200);
64 expect(updateRes.body.value).toBe(99);
65 });
66 });
67
68 describe('Error scenarios', () => {
69 it('handles all expected error cases', async () => {
70 const cases = [
71 { path: '/api/resources/non-existent', expectedStatus: 404 },
72 { path: '/api/resources', expectedStatus: 401, skipAuth: true },
73 ];
74
75 for (const tc of cases) {
76 const req = request(app.getHttpServer()).get(tc.path);
77 if (!tc.skipAuth) req.set('Authorization', 'Bearer ' + authToken);
78 const res = await req;
79 expect(res.status).toBe(tc.expectedStatus);
80 }
81 });
82 });
83});This test suite implements the core patterns for grpc api testing with k6 and grpcurl: unique TEST_RUN prefix for cleanup isolation, token-based authentication in beforeAll, happy path workflow testing with chained requests, and systematic error case coverage. The approach ensures tests are independent, deterministic, and leave no orphaned data in the test database.
The next lesson continues building comprehensive API test coverage with additional advanced patterns and real-world scenarios.
Your progress is saved in this browser, on this device only.