Claude Code Plugins

Community-maintained marketplace

Feedback

API testing strategies and contract testing

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name testing
version 2.0.0
description API testing strategies and contract testing
sasmp_version 1.3.0
bonded_agent 05-security-compliance
bond_type PRIMARY_BOND
atomic_design [object Object]
parameter_validation [object Object]
retry_config [object Object]
logging [object Object]
dependencies [object Object]

API Testing Skill

Purpose

Implement comprehensive API testing strategies.

Testing Pyramid

         ╱╲
        ╱E2E╲        Few, slow, expensive
       ╱──────╲
      ╱ Contract╲    Consumer-driven contracts
     ╱────────────╲
    ╱ Integration  ╲  API + Database
   ╱────────────────╲
  ╱     Unit Tests   ╲ Fast, many, cheap
 ╱────────────────────╲

Unit Testing

Controller/Handler Tests

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserController } from './user.controller';
import { UserService } from './user.service';

describe('UserController', () => {
  let controller: UserController;
  let mockUserService: jest.Mocked<UserService>;

  beforeEach(() => {
    mockUserService = {
      findById: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    };
    controller = new UserController(mockUserService);
  });

  describe('getUser', () => {
    it('should return user when found', async () => {
      const mockUser = { id: '123', name: 'John', email: 'john@test.com' };
      mockUserService.findById.mockResolvedValue(mockUser);

      const result = await controller.getUser('123');

      expect(result).toEqual({ data: mockUser });
      expect(mockUserService.findById).toHaveBeenCalledWith('123');
    });

    it('should throw NotFoundException when user not found', async () => {
      mockUserService.findById.mockResolvedValue(null);

      await expect(controller.getUser('999')).rejects.toThrow('User not found');
    });
  });

  describe('createUser', () => {
    it('should create and return new user', async () => {
      const input = { email: 'new@test.com', name: 'New User' };
      const created = { id: '456', ...input };
      mockUserService.create.mockResolvedValue(created);

      const result = await controller.createUser(input);

      expect(result.data.id).toBe('456');
    });

    it('should validate input', async () => {
      const invalidInput = { email: 'invalid', name: '' };

      await expect(controller.createUser(invalidInput)).rejects.toThrow('Validation');
    });
  });
});

Service/Business Logic Tests

describe('UserService', () => {
  let service: UserService;
  let mockRepository: jest.Mocked<UserRepository>;

  beforeEach(() => {
    mockRepository = {
      findById: vi.fn(),
      save: vi.fn(),
      delete: vi.fn(),
    };
    service = new UserService(mockRepository);
  });

  describe('create', () => {
    it('should hash password before saving', async () => {
      const input = { email: 'test@test.com', name: 'Test', password: 'secret123' };

      await service.create(input);

      expect(mockRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          email: 'test@test.com',
          passwordHash: expect.not.stringContaining('secret123'),
        })
      );
    });

    it('should throw ConflictError for duplicate email', async () => {
      mockRepository.save.mockRejectedValue({ code: '23505' }); // Unique violation

      await expect(service.create({ email: 'existing@test.com' }))
        .rejects.toThrow('Email already exists');
    });
  });
});

Integration Testing

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from './app';
import { db } from './database';

describe('Users API Integration', () => {
  beforeAll(async () => {
    await db.migrate.latest();
    await db.seed.run();
  });

  afterAll(async () => {
    await db.destroy();
  });

  describe('GET /api/v1/users', () => {
    it('should return paginated list', async () => {
      const res = await request(app)
        .get('/api/v1/users?page=1&limit=10')
        .set('Authorization', `Bearer ${testToken}`)
        .expect(200);

      expect(res.body.data).toBeInstanceOf(Array);
      expect(res.body.pagination).toMatchObject({
        page: 1,
        limit: 10,
        total: expect.any(Number),
      });
    });
  });

  describe('POST /api/v1/users', () => {
    it('should persist user to database', async () => {
      const userData = {
        email: 'integration@test.com',
        name: 'Integration Test',
        password: 'SecurePass123!',
      };

      const res = await request(app)
        .post('/api/v1/users')
        .set('Authorization', `Bearer ${adminToken}`)
        .send(userData)
        .expect(201);

      // Verify in database
      const dbUser = await db('users')
        .where('id', res.body.data.id)
        .first();

      expect(dbUser).toBeDefined();
      expect(dbUser.email).toBe(userData.email);
    });
  });
});

Contract Testing (Pact)

Consumer Side

import { Pact } from '@pact-foundation/pact';
import { UserApiClient } from './user-api-client';

describe('User API Contract', () => {
  const provider = new Pact({
    consumer: 'WebApp',
    provider: 'UserService',
    port: 1234,
  });

  beforeAll(() => provider.setup());
  afterAll(() => provider.finalize());
  afterEach(() => provider.verify());

  describe('get user by id', () => {
    it('should return user when exists', async () => {
      await provider.addInteraction({
        state: 'user with id 123 exists',
        uponReceiving: 'a request for user 123',
        withRequest: {
          method: 'GET',
          path: '/api/v1/users/123',
          headers: { Accept: 'application/json' },
        },
        willRespondWith: {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
          body: {
            data: {
              id: '123',
              name: like('John Doe'),
              email: like('john@example.com'),
            },
          },
        },
      });

      const client = new UserApiClient('http://localhost:1234');
      const user = await client.getUser('123');

      expect(user.id).toBe('123');
    });
  });
});

Provider Side

import { Verifier } from '@pact-foundation/pact';

describe('Pact Verification', () => {
  it('should validate consumer contracts', async () => {
    const verifier = new Verifier({
      providerBaseUrl: 'http://localhost:3000',
      pactBrokerUrl: process.env.PACT_BROKER_URL,
      provider: 'UserService',
      publishVerificationResult: true,
      stateHandlers: {
        'user with id 123 exists': async () => {
          await db('users').insert({ id: '123', name: 'John', email: 'john@test.com' });
        },
      },
    });

    await verifier.verifyProvider();
  });
});

Load Testing (k6)

// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },   // Ramp up
    { duration: '3m', target: 50 },   // Steady state
    { duration: '1m', target: 100 },  // Peak load
    { duration: '1m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% under 500ms
    http_req_failed: ['rate<0.01'],    // <1% error rate
  },
};

export default function () {
  const token = __ENV.API_TOKEN;

  // List users
  const listRes = http.get('http://api.example.com/api/v1/users?limit=20', {
    headers: { Authorization: `Bearer ${token}` },
  });

  check(listRes, {
    'list status 200': (r) => r.status === 200,
    'list has data': (r) => JSON.parse(r.body).data.length > 0,
  });

  sleep(1);

  // Get single user
  const getRes = http.get('http://api.example.com/api/v1/users/123', {
    headers: { Authorization: `Bearer ${token}` },
  });

  check(getRes, {
    'get status 200': (r) => r.status === 200,
  });

  sleep(1);
}

Run: k6 run --env API_TOKEN=xxx load-test.js

Security Testing

describe('Security Tests', () => {
  describe('Authentication', () => {
    it('should reject requests without token', async () => {
      await request(app)
        .get('/api/v1/users')
        .expect(401);
    });

    it('should reject expired tokens', async () => {
      await request(app)
        .get('/api/v1/users')
        .set('Authorization', `Bearer ${expiredToken}`)
        .expect(401);
    });
  });

  describe('Authorization', () => {
    it('should prevent accessing other users data', async () => {
      await request(app)
        .get('/api/v1/users/other-user-id')
        .set('Authorization', `Bearer ${userToken}`)
        .expect(403);
    });
  });

  describe('Input Validation', () => {
    it('should reject SQL injection attempts', async () => {
      await request(app)
        .get(`/api/v1/users?search='; DROP TABLE users; --`)
        .set('Authorization', `Bearer ${token}`)
        .expect(400);
    });

    it('should sanitize XSS in input', async () => {
      const res = await request(app)
        .post('/api/v1/users')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({ name: '<script>alert("xss")</script>' })
        .expect(201);

      expect(res.body.data.name).not.toContain('<script>');
    });
  });

  describe('Rate Limiting', () => {
    it('should block after exceeding limit', async () => {
      // Make many requests
      for (let i = 0; i < 100; i++) {
        await request(app).get('/api/v1/users');
      }

      // Should be rate limited
      await request(app)
        .get('/api/v1/users')
        .expect(429);
    });
  });
});

Troubleshooting

Issue Cause Solution
Flaky tests Shared state Isolate test data
Slow integration tests Database setup Use transactions
Contract mismatches Schema changes Version contracts
Load test failures Connection limits Check pool size

Quality Checklist

  • Unit test coverage > 80%
  • Integration tests for critical paths
  • Contract tests with consumers
  • Load tests before releases
  • Security tests automated
  • Tests run in CI/CD
  • Test data isolated
  • Mock external services