Lesson 20-Serverless Development Tools and Frameworks

Serverless Framework In-Depth

Serverless.yml Configuration

The core configuration file serverless.yml in Serverless Framework is key to defining the entire service architecture. This YAML file includes service metadata, resource definitions, function configurations, and various trigger settings.

Basic Configuration Structure

A typical serverless.yml file includes the following main sections:

service: my-serverless-service  # Service name

provider:
  name: aws                     # Cloud provider
  runtime: nodejs14.x           # Runtime environment
  region: us-east-1             # Default region
  stage: dev                    # Default stage
  profile: default              # AWS CLI profile
  
  environment:
    VARIABLE1: value1           # Environment variables
    VARIABLE2: value2

  iamRoleStatements:            # IAM permission policies
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:us-east-1:123456789012:table/my-table"

functions:
  helloWorld:
    handler: handler.helloWorld # Function handler path
    events:                     # Trigger configuration
      - http:
          path: hello
          method: get
          cors: true

plugins:
  - serverless-offline          # Plugin list
  - serverless-dotenv-plugin

Advanced Configuration Options

Custom Domain Configuration:

custom:
  customDomain:
    domainName: api.myapp.com
    basePath: ''
    stage: ${self:provider.stage}
    createRoute53Record: true

Resource Definition:

resources:
  Resources:
    MyDynamoDBTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: MyTable
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 5
          WriteCapacityUnits: 5

Stage Configuration Overrides:

provider:
  name: aws
  runtime: nodejs14.x
  stage: ${opt:stage, 'dev'}  # Get stage from CLI, default to dev

functions:
  helloWorld:
    handler: handler.helloWorld
    environment:
      STAGE: ${self:provider.stage}  # Use current stage value

Multi-Environment Deployment

In real-world projects, deployment across different environments (development, testing, production) is common. Serverless Framework offers multiple ways to manage multi-environment configurations.

Environment Variable Management

Method 1: Define Directly in serverless.yml:

provider:
  environment:
    NODE_ENV: ${opt:stage, self:custom.defaultStage}
    DB_HOST: ${env:DB_HOST, 'localhost'}

Method 2: Use .env Files:

Create .env.dev, .env.prod, etc.:

# .env.dev
DB_HOST=localhost
DB_PORT=5432

# .env.prod
DB_HOST=db.example.com
DB_PORT=5432

Reference in serverless.yml:

plugins:
  - serverless-dotenv-plugin

custom:
  dotenv:
    include:
      - DB_HOST
      - DB_PORT

Method 3: Use serverless-pseudo-parameters Plugin:

Install plugin:

npm install --save-dev serverless-pseudo-parameters

Configure usage:

provider:
  environment:
    TABLE_NAME: !Ref MyDynamoDBTable
    BUCKET_NAME: !Sub "my-bucket-${opt:stage, self:provider.stage}"

Environment-Specific Deployment Commands

# Development environment deployment
serverless deploy --stage dev

# Testing environment deployment
serverless deploy --stage test

# Production environment deployment
serverless deploy --stage prod

Conditional Resource Creation

resources:
  Resources:
    DevOnlyResource:
      Type: AWS::S3::Bucket
      Condition: IsDevStage
    ProdOnlyResource:
      Type: AWS::S3::Bucket
      Condition: IsProdStage

conditions:
  IsDevStage: !Equals [!Ref "AWS::StackName", "dev"]
  IsProdStage: !Equals [!Ref "AWS::StackName", "prod"]

Note: The above condition example is incorrect. A correct approach would be:

custom:
  isDev: ${opt:stage, 'dev'} == 'dev'
  isProd: ${opt:stage, 'dev'} == 'prod'

resources:
  Resources:
    DevOnlyResource:
      Type: AWS::S3::Bucket
      Condition: IsDev
    ProdOnlyResource:
      Type: AWS::S3::Bucket
      Condition: IsProd

conditions:
  IsDev: !Equals ["${self:custom.isDev}", "true"]
  IsProd: !Equals ["${self:custom.isProd}", "true"]

A more practical approach is:

  1. Use separate configuration files
  2. Use plugins like serverless-plugin-ifelse
  3. Handle conditional logic in deployment scripts

Using serverless-plugin-ifelse for Conditional Deployment

Install plugin:

npm install --save-dev serverless-plugin-ifelse

Configure:

plugins:
  - serverless-plugin-ifelse

custom:
  ifelse:
    - If: '${opt:stage} == "dev"'
      Set:
        provider.environment.DEBUG: 'true'
        resources.Resources.DevOnlyResource.Type: 'AWS::S3::Bucket'
    - If: '${opt:stage} == "prod"'
      Set:
        provider.environment.DEBUG: 'false'
        resources.Resources.ProdOnlyResource.Type: 'AWS::S3::Bucket'

Note: The above configuration syntax may be incorrect; refer to the plugin’s documentation for accurate usage.

A more reliable method is to use separate serverless.yml files or handle logic in deployment scripts.

Plugins and Extensions

Serverless Framework’s strength lies in its rich plugin ecosystem, which extends functionality, integrates third-party services, or adds custom behaviors.

Common Plugin Categories

  1. Deployment Optimization:
    • serverless-webpack: Bundle functions with Webpack
    • serverless-bundle: Modern bundling solution
    • serverless-offline: Simulate API Gateway locally
  2. Environment Management:
    • serverless-dotenv-plugin: Load .env files
    • serverless-pseudo-parameters: Use AWS pseudo-parameters
  3. Monitoring and Logging:
    • serverless-plugin-log-retention: Set CloudWatch log retention policies
    • serverless-plugin-canary-deployments: Canary deployments
  4. Security:
    • serverless-patch-plugin: Modify generated CloudFormation templates
    • serverless-iam-roles-per-function: Create per-function IAM roles
  5. Custom:
    • serverless-plugin-ifelse: Conditional logic
    • serverless-scriptable-plugin: Run custom scripts

Plugin Installation and Configuration Examples

serverless-webpack Plugin:

Install:

npm install --save-dev serverless-webpack webpack webpack-node-externals

Configure serverless.yml:

plugins:
  - serverless-webpack

custom:
  webpack:
    includeModules:
      forceExclude:
        - aws-sdk
    packager: 'yarn'

serverless-offline Plugin:

Install:

npm install --save-dev serverless-offline

Configure serverless.yml:

plugins:
  - serverless-offline

custom:
  serverless-offline:
    port: 3000
    host: 0.0.0.0
    httpPort: 4000
    httpsProtocol: 'dev-certs'

serverless-bundle Plugin:

Install:

npm install --save-dev serverless-bundle

Configure serverless.yml:

plugins:
  - serverless-bundle

custom:
  bundle:
    linting: false
    sourcemaps: true
    externalModules:
      - aws-sdk

Custom Plugin Development

Steps to create a custom plugin:

  1. Create plugin directory structure:
my-plugin/
  ├── index.js
  └── package.json
  1. Write plugin code (index.js):
'use strict';

class MyPlugin {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;

    this.hooks = {
      'before:deploy:deploy': this.beforeDeploy.bind(this),
    };
  }

  async beforeDeploy() {
    this.serverless.cli.log('Running custom pre-deploy logic...');
    // Add custom logic
  }
}

module.exports = MyPlugin;
  1. Use in serverless.yml:
plugins:
  - ./my-plugin

Plugin Chain and Execution Order

Plugins execute in the order listed in the plugins array. Hooks control execution timing:

plugins:
  - plugin-a
  - plugin-b

Example hooks plugins can define:

  • before:deploy:deploy
  • after:deploy:deploy
  • before:invoke:invoke
  • after:invoke:invoke

Multi-Stage Deployment Strategies

For different environments (development, testing, production), Serverless Framework provides multiple configuration options.

Environment Variable Management Best Practices

  1. Layered Environment Variables:
    • Global variables (shared across stages)
    • Stage-specific variables
    • Local development variables
  2. Secure Storage:
    • Use AWS Secrets Manager for sensitive data
    • Store non-sensitive data in .env files or directly in serverless.yml
  3. Dynamic Loading:
    • Load configurations dynamically based on deployment stage

Deployment Workflow Example

  1. Development Environment:
serverless deploy --stage dev --aws-profile dev-account
  1. Testing Environment:
serverless deploy --stage test --aws-profile test-account
  1. Production Environment:
serverless deploy --stage prod --aws-profile prod-account

Conditional Resource Creation Example

resources:
  Resources:
    DevOnlyResource:
      Type: AWS::S3::Bucket
      Condition: CreateDevResources

    ProdOnlyResource:
      Type: AWS::S3::Bucket
      Condition: CreateProdResources

conditions:
  CreateDevResources: !Equals ["${opt:stage, 'dev'}", "dev"]
  CreateProdResources: !Equals ["${opt:stage, 'dev'}", "prod"]

Multi-Account Deployment

Configure multiple AWS accounts:

# ~/.aws/credentials
[dev-account]
aws_access_key_id = DEV_ACCESS_KEY
aws_secret_access_key = DEV_SECRET_KEY

[test-account]
aws_access_key_id = TEST_ACCESS_KEY
aws_secret_access_key = TEST_SECRET_KEY

[prod-account]
aws_access_key_id = PROD_ACCESS_KEY
aws_secret_access_key = PROD_SECRET_KEY

Deployment commands:

# Development environment
serverless deploy --stage dev --aws-profile dev-account

# Testing environment
serverless deploy --stage test --aws-profile test-account

# Production environment
serverless deploy --stage prod --aws-profile prod-account

Blue-Green Deployment Strategy

Implement blue-green deployments with AWS CodeDeploy:

  1. Configure CodeDeploy:
provider:
  name: aws
  deploymentBucket:
    name: my-deployment-bucket
  deployment:
    type: CodeDeploy
    deploymentGroupName: my-deployment-group
  1. Deploy:
serverless deploy --stage prod
  1. Switch traffic manually or automatically:
  • Use ALB/ELB to switch target groups
  • Use Route53 weighted routing

Cloud-Native Tool Integration

AWS SAM (Serverless Application Model)

AWS SAM is AWS’s official Serverless Application Model, extending CloudFormation template syntax to simplify Serverless application definition and deployment.

SAM Basic Configuration

SAM Template Structure:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs14.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

SAM CLI Usage

  1. Install SAM CLI:
# macOS
brew tap aws/tap
brew install aws-sam-cli

# Windows
choco install aws-sam-cli

# Linux
curl -Lo sam-installation.zip https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip sam-installation.zip -d sam-installation
sudo ./sam-installation/install
  1. Local Testing:
sam build
sam local start-api
  1. Deployment:
sam deploy --guided

SAM vs. Serverless Framework Comparison

FeatureAWS SAMServerless Framework
Definition MethodCloudFormation extensionCustom YAML format
Plugin EcosystemLimitedRich
Multi-Cloud SupportAWS onlyMulti-cloud support
Learning CurveSteeperGentler
Advanced FeaturesLimitedRich
Community SupportOfficial supportCommunity-driven

Hybrid Usage Example

Integrate SAM resources in a Serverless Framework project:

  1. Create sam-resources.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  MySAMFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: sam-function/
      Handler: app.lambdaHandler
      Runtime: nodejs14.x
  1. Reference in serverless.yml:
resources:
  - ${file(sam-resources.yaml)}

Note: This method may not be fully compatible and requires testing.

Azure Functions Core Tools

Azure Functions Core Tools is a CLI for developing and managing Azure Functions.

Installation and Configuration

  1. Installation:
# Windows
npm install -g azure-functions-core-tools@4 --unsafe-perm true

# macOS/Linux
brew tap azure/functions
brew install azure-functions-core-tools@4
# or
npm install -g azure-functions-core-tools@4 --unsafe-perm true
  1. Local Run:
func start
  1. Create Function:
func init MyFunctionProj --typescript
cd MyFunctionProj
func new --name HttpExample --template "HTTP trigger"

Configuration Files

host.json example:

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[2.*, 3.0.0)"
  }
}

local.settings.json example:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "MY_SETTING": "value"
  }
}

Deploy to Azure

  1. Login:
az login
  1. Deploy:
func azure functionapp publish <FunctionAppName>

Google Cloud SDK

Google Cloud SDK is a CLI for managing Google Cloud resources.

Installation and Configuration

  1. Installation:
# Linux
curl https://sdk.cloud.google.com | bash
exec -l $SHELL
gcloud init

# macOS
brew install --cask google-cloud-sdk
gcloud init

# Windows
choco install google-cloud-sdk
gcloud init
  1. Configure Project:
gcloud config set project [PROJECT_ID]

Deploy Cloud Functions

  1. Create Function:
gcloud functions deploy helloWorld \
  --runtime nodejs14 \
  --trigger-http \
  --allow-unauthenticated
  1. View Function:
gcloud functions describe helloWorld
  1. Invoke Function:
gcloud functions call helloWorld --data '{"name":"World"}'

Configuration File

cloudbuild.yaml example:

steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  args: ['gcloud', 'functions', 'deploy', 'helloWorld', 
         '--runtime', 'nodejs14', 
         '--trigger-http', 
         '--allow-unauthenticated']

Local Development and Testing

LocalStack and Service Simulation

LocalStack is a fully managed local AWS service implementation for development and testing.

Installation and Configuration

  1. Install Docker:
# macOS
brew install docker

# Ubuntu
sudo apt-get update
sudo apt-get install docker.io
  1. Run LocalStack:
docker run --rm -it -p 4566:4566 -p 4571:4571 localstack/localstack
  1. Configure AWS CLI:
aws --endpoint-url=http://localhost:4566 configure

Using LocalStack to Simulate Services

  1. S3 Simulation:
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url=http://localhost:4566 s3 ls
  1. Lambda Simulation:
# Create Lambda function
aws --endpoint-url=http://localhost:4566 lambda create-function \
  --function-name my-function \
  --runtime python3.8 \
  --role arn:aws:iam::000000000000:role/lambda-execution-role \
  --handler lambda_function.handler \
  --zip-file fileb://function.zip

# Invoke Lambda function
aws --endpoint-url=http://localhost:4566 lambda invoke \
  --function-name my-function \
  output.json

Serverless Offline

The Serverless Offline plugin allows local simulation of API Gateway and Lambda functions.

Installation and Configuration

  1. Install Plugin:
npm install --save-dev serverless-offline
  1. Configure serverless.yml:
plugins:
  - serverless-offline

custom:
  serverless-offline:
    port: 3000
    host: 0.0.0.0
    httpPort: 4000
    httpsProtocol: 'dev-certs'
  1. Run Local Service:
serverless offline start

Advanced Features

  1. Simulate Lambda Events:
serverless invoke local --function helloWorld --path event.json
  1. Simulate Multiple Lambdas:
functions:
  func1:
    handler: handler.func1
    events:
      - http:
          path: func1
          method: get

  func2:
    handler: handler.func2
    events:
      - http:
          path: func2
          method: post
  1. Simulate DynamoDB:
custom:
  serverless-offline:
    dynamodb:
      start:
        port: 8000
        inMemory: true
        migrate: true

Testing Tools and Mocks

Unit Testing

  1. Jest Configuration:
npm install --save-dev jest @types/jest ts-jest
  1. jest.config.js:
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  coverageDirectory: 'coverage',
  collectCoverageFrom: ['src/**/*.{js,ts}'],
};
  1. Test Example:
// handler.test.ts
import { handler } from '../src/handler';

describe('Handler', () => {
  it('should return hello world', async () => {
    const event = { key: 'value' };
    const result = await handler(event);
    expect(result).toEqual({ statusCode: 200, body: 'Hello World' });
  });
});

Integration Testing

  1. Using Serverless Offline:
serverless offline start &
sleep 5 # Wait for service to start
npm test
kill %1 # Stop service
  1. Using Docker Compose:
version: '3'
services:
  serverless:
    build: .
    command: sls offline start
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    environment:
      - NODE_ENV=test

Mock Services

  1. Mock DynamoDB:
// mockDynamo.js
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();

jest.mock('aws-sdk', () => ({
  DynamoDB: {
    DocumentClient: jest.fn(() => ({
      get: jest.fn((params, callback) => {
        callback(null, { Item: { id: params.Key.id, value: 'mocked' } });
      }),
      put: jest.fn((params, callback) => {
        callback(null, {});
      }),
    })),
  },
}));

module.exports = dynamoDb;
  1. Mock API Gateway:
// mockApiGateway.js
const mockResponse = (statusCode, body) => ({
  statusCode,
  body: JSON.stringify(body),
});

const mockLambdaContext = {
  succeed: (response) => console.log('Success:', response),
  fail: (error) => console.error('Error:', error),
};

module.exports = { mockResponse, mockLambdaContext };

Test Coverage

  1. Generate Coverage Report:
jest --coverage
  1. Configure Coverage Thresholds:
// jest.config.js
module.exports = {
  // ...
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

Continuous Integration Testing

  1. GitHub Actions Configuration:
name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 16.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm test
    - run: npm run coverage
  1. CodeCov Integration:
- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v1

Test Data Management

  1. Using Factory Boy (JavaScript Version):
// factory.js
class Factory {
  static createUser(overrides = {}) {
    return {
      id: Math.random().toString(36).substr(2, 9),
      name: 'Test User',
      email: 'test@example.com',
      ...overrides,
    };
  }
}

module.exports = Factory;
  1. Using Faker.js for Test Data:
const faker = require('faker');

const generateTestData = () => ({
  user: {
    id: faker.datatype.uuid(),
    name: faker.name.findName(),
    email: faker.internet.email(),
  },
  product: {
    id: faker.datatype.uuid(),
    name: faker.commerce.productName(),
    price: faker.commerce.price(),
  },
});

Test Environment Isolation

  1. Using Docker Compose for Isolated Environments:
version: '3'
services:
  dynamodb:
    image: amazon/dynamodb-local
    ports:
      - "8000:8000"
    command: "-jar DynamoDBLocal.jar -sharedDb -inMemory"

  redis:
    image: redis
    ports:
      - "6379:6379"
  1. Using Testcontainers (Java) or dockerode (Node.js):
const Docker = require('dockerode');
const docker = new Docker();

async function startTestDynamoDB() {
  const container = await docker.createContainer({
    Image: 'amazon/dynamodb-local',
    Cmd: ['-jar', 'DynamoDBLocal.jar', '-sharedDb', '-inMemory'],
    HostConfig: {
      PortBindings: {
        '8000/tcp': [{ HostPort: '8000' }],
      },
    },
  });
  
  await container.start();
  return `http://localhost:8000`;
}

Performance and Load Testing

  1. Using Artillery for Load Testing:
npm install -g artillery
# load-test.yml
config:
  target: "http://localhost:3000"
  phases:
    - duration: 60
      arrivalRate: 10
scenarios:
  - flow:
      - get:
          url: "/api/hello"

Run test:

artillery run load-test.yml
  1. Using k6 for Performance Testing:
npm install -g k6
// script.js
import http from 'k6/http';

export default function () {
  http.get('http://localhost:3000/api/hello');
}

Run test:

k6 run --vus 10 --duration 60s script.js

Security and Authentication Testing

  1. Simulating JWT Authentication:
// auth-mock.js
module.exports = (req, res, next) => {
  req.headers.authorization = 'Bearer mock.jwt.token';
  next();
};
  1. Testing IAM Permissions:
const AWS = require('aws-sdk-mock');
const AWS_SDK = require('aws-sdk');

AWS.mock('IAM', 'getRole', (params, callback) => {
  callback(null, { Role: { Arn: 'arn:aws:iam::123456789012:role/MockRole' } });
});

// Test code
const iam = new AWS_SDK.IAM();
iam.getRole({ RoleName: 'TestRole' }, (err, data) => {
  console.log(data); // { Role: { Arn: 'arn:aws:iam::123456789012:role/MockRole' } }
});

Event Trigger Testing

  1. Simulating S3 Events:
// s3-event.json
{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "2021-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": { "principalId": "AWS:AIDAJDPLRKLG7UEXAMPLE" },
      "requestParameters": { "sourceIPAddress": "192.0.2.1" },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "example-bucket",
          "ownerIdentity": { "principalId": "A3NL1KOZZKExample" },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "test/key",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}
  1. Testing DynamoDB Stream Events:
// dynamodb-event.json
{
  "Records": [
    {
      "eventID": "1",
      "eventName": "INSERT",
      "eventVersion": "1.1",
      "eventSource": "aws:dynamodb",
      "awsRegion": "us-east-1",
      "dynamodb": {
        "ApproximateCreationDateTime": 1609459200,
        "Keys": {
          "id": {
            "S": "123"
          }
        },
        "NewImage": {
          "id": {
            "S": "123"
          },
          "name": {
            "S": "Test Item"
          }
        },
        "SequenceNumber": "111",
        "SizeBytes": 26,
        "StreamViewType": "NEW_AND_OLD_IMAGES"
      },
      "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTable/stream/2021-01-01T00:00:00.000"
    }
  ]
}

Error Handling Testing

  1. Simulating Lambda Errors:
// error-mock.js
module.exports = (err) => {
  const error = new Error('Mocked Error');
  error.code = 'MockedErrorCode';
  error.statusCode = 500;
  return error;
};
  1. Testing Retry Logic:
describe('Retry Logic', () => {
  it('should retry failed operation', async () => {
    const retryOperation = jest.fn()
      .mockRejectedValueOnce(new Error('First failure'))
      .mockResolvedValueOnce('Success');
    
    const result = await withRetry(retryOperation, 3);
    expect(result).toBe('Success');
    expect(retryOperation).toHaveBeenCalledTimes(2);
  });
});

Data Consistency Testing

  1. Transaction Testing:
describe('Database Transactions', () => {
  it('should commit transaction on success', async () => {
    const result = await performTransaction();
    expect(result).toBe(true);
    const dbData = await getFromDatabase();
    expect(dbData).toEqual(expectedData);
  });
  
  it('should rollback transaction on failure', async () => {
    await expect(failingTransaction()).rejects.toThrow();
    const dbData = await getFromDatabase();
    expect(dbData).not.toEqual(expectedData);
  });
});
  1. Concurrency Testing:
describe('Concurrent Operations', () => {
  it('should handle concurrent updates', async () => {
    const promises = Array(10).fill().map(() => updateResource());
    await Promise.all(promises);
    const finalState = await getResource();
    expect(finalState.version).toBe(10); // Assumes version increments per update
  });
});

Test Environment Cleanup

  1. Automatic Test Data Cleanup:
afterEach(async () => {
  await cleanupDatabase();
  await deleteS3Objects();
});

async function cleanupDatabase() {
  const items = await scanDatabase();
  for (const item of items) {
    await deleteFromDatabase(item.id);
  }
}

async function deleteS3Objects() {
  const objects = await listS3Objects();
  for (const obj of objects) {
    await deleteS3Object(obj.key);
  }
}
  1. Using Docker Compose for Cleanup:
version: '3'
services:
  test-cleanup:
    image: alpine
    volumes:
      - ./test-data:/data
    command: >
      sh -c "rm -rf /data/* && echo 'Test data cleaned up'"
    depends_on:
      - app

Logging and Monitoring Testing

  1. Structured Logging:
// Structured logging with Winston
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'test.log' })
  ]
});

// Use in tests
logger.info('Test started', { testId: '123', timestamp: new Date() });
  1. Test Coverage Monitoring:
# Generate coverage report
jest --coverage

# Upload to Coveralls
cat ./coverage/lcov.info | coveralls

Performance Benchmark Testing

  1. Establishing Performance Baseline:
describe('Performance Baseline', () => {
  it('should process 1000 requests in under 5 seconds', async () => {
    const start = Date.now();
    const promises = Array(1000).fill().map(() => makeRequest());
    await Promise.all(promises);
    const duration = Date.now() - start;
    expect(duration).toBeLessThan(5000);
  });
});
  1. Comparing Performance Across Versions:
# Benchmark with wrk
wrk -t12 -c400 -d30s http://localhost:3000/api/endpoint

# Record results and compare versions

Security Vulnerability Testing

  1. Static Code Analysis:
# Code quality check with ESLint
eslint . --ext .js,.ts

# Security scan with SonarQube
sonar-scanner
  1. Dynamic Security Testing:
# Security scan with OWASP ZAP
zap-cli quick-scan --self-contained --start --spider --ajax-spider --scanners all http://localhost:3000

# Manual testing with Burp Suite

Chaos Engineering Testing

  1. Simulating Failures:
// Simulate service failures with Chaos Monkey
const chaosMonkey = require('chaos-monkey');

describe('Chaos Engineering', () => {
  it('should handle service failures gracefully', () => {
    chaosMonkey.enable();
    chaosMonkey.addRule({
      probability: 0.5,
      target: 'userService',
      method: 'GET'
    });
    
    // Test code
    await expect(makeRequest()).rejects.toThrow();
  });
});
  1. Network Latency Testing:
# Simulate network latency with tc
sudo tc qdisc add dev eth0 root netem delay 100ms

# Run tests
jest

# Cleanup
sudo tc qdisc del dev eth0 root

Internationalization and Localization Testing

  1. Multi-Language Testing:
describe('Internationalization', () => {
  it('should display messages in correct language', () => {
    const messages = getMessages('es');
    expect(messages.welcome).toBe('Bienvenido');
    
    const messagesFr = getMessages('fr');
    expect(messagesFr.welcome).toBe('Bienvenue');
  });
});
Share your love