CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-loopback--testlab

A collection of test utilities specifically designed for LoopBack 4 applications and TypeScript testing

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

validation-helpers.mddocs/

Validation and Helpers

OpenAPI/Swagger specification validation, JSON conversion utilities, test skipping helpers, and HTTP error logging.

Capabilities

API Specification Validation

OpenAPI/Swagger specification validation using the oas-validator library.

/**
 * Validates OpenAPI/Swagger specifications for correctness
 * @param spec - OpenAPI specification object to validate
 * @throws Error if specification is invalid
 */
function validateApiSpec(spec: any): Promise<void>;

Usage Examples:

import { validateApiSpec, expect } from "@loopback/testlab";

// Valid OpenAPI 3.0 specification
const validSpec = {
  openapi: "3.0.0",
  info: {
    title: "Test API",
    version: "1.0.0"
  },
  paths: {
    "/users": {
      get: {
        responses: {
          "200": {
            description: "List of users",
            content: {
              "application/json": {
                schema: {
                  type: "array",
                  items: {
                    type: "object",
                    properties: {
                      id: { type: "string" },
                      name: { type: "string" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
};

// Validate specification - should not throw
await validateApiSpec(validSpec);

// Test invalid specification
const invalidSpec = {
  openapi: "3.0.0",
  // Missing required 'info' property
  paths: {}
};

try {
  await validateApiSpec(invalidSpec);
  throw new Error("Should have thrown validation error");
} catch (error) {
  expect(error.message).to.match(/invalid/i);
}

// Use in API testing
describe("API Specification Tests", () => {
  it("should have valid OpenAPI spec", async () => {
    const spec = loadApiSpecification();
    await validateApiSpec(spec);
  });

  it("should validate generated spec", async () => {
    const app = createTestApplication();
    const spec = app.getApiSpecification();
    await validateApiSpec(spec);
  });
});

JSON Conversion

Utility function for converting values to JSON-serializable format, handling edge cases.

/**
 * JSON encoding utility that handles edge cases
 * Converts values to JSON-serializable format by removing undefined properties
 * and handling special types like Date and Function
 */
function toJSON<T>(value: T): T;

// Specific overloads for different types
function toJSON(value: Date): string;
function toJSON(value: Function): undefined;
function toJSON(value: unknown[]): unknown[];
function toJSON(value: object): object;
function toJSON(value: undefined): undefined;
function toJSON(value: null): null;
function toJSON(value: number): number;
function toJSON(value: boolean): boolean;
function toJSON(value: string): string;

// Union type overloads
function toJSON(value: unknown[] | null): unknown[] | null;
function toJSON(value: unknown[] | undefined): unknown[] | undefined;
function toJSON(value: unknown[] | null | undefined): unknown[] | null | undefined;
function toJSON(value: object | null): object | null;
function toJSON(value: object | undefined): object | undefined;
function toJSON(value: object | null | undefined): object | null | undefined;

Usage Examples:

import { toJSON, expect } from "@loopback/testlab";

// Handle Date objects
const date = new Date("2023-01-01T00:00:00.000Z");
const dateJson = toJSON(date);
expect(dateJson).to.equal("2023-01-01T00:00:00.000Z");

// Handle Functions (converted to undefined)
const func = () => "hello";
const funcJson = toJSON(func);
expect(funcJson).to.be.undefined();

// Handle objects with undefined properties
const objWithUndefined = {
  name: "Alice",
  age: undefined,
  active: true,
  metadata: {
    created: new Date("2023-01-01"),
    updated: undefined
  }
};

const cleanObj = toJSON(objWithUndefined);
// undefined properties are removed during JSON conversion
expect(cleanObj).to.not.have.property("age");
expect(cleanObj.metadata).to.not.have.property("updated");
expect(cleanObj.name).to.equal("Alice");
expect(cleanObj.active).to.be.true();

// Handle arrays
const mixedArray = [1, "hello", new Date("2023-01-01"), undefined, null];
const cleanArray = toJSON(mixedArray);
expect(cleanArray).to.eql([1, "hello", "2023-01-01T00:00:00.000Z", null, null]);

// Use in API testing for comparing expected vs actual responses
const expectedResponse = {
  id: 123,
  name: "Test User",
  createdAt: new Date("2023-01-01"),
  metadata: undefined // This will be removed
};

const apiResponse = {
  id: 123,
  name: "Test User",
  createdAt: "2023-01-01T00:00:00.000Z"
  // No metadata property
};

// Compare using toJSON to normalize
expect(toJSON(expectedResponse)).to.eql(apiResponse);

// Handle nested objects and arrays
const complexData = {
  users: [
    {
      id: 1,
      name: "Alice",
      lastLogin: new Date("2023-01-01"),
      preferences: undefined
    },
    {
      id: 2,
      name: "Bob",
      lastLogin: new Date("2023-01-02"),
      preferences: {theme: "dark", notifications: true}
    }
  ],
  metadata: {
    total: 2,
    generated: new Date("2023-01-03"),
    cached: undefined
  }
};

const normalizedData = toJSON(complexData);
expect(normalizedData.users[0]).to.not.have.property("preferences");
expect(normalizedData.users[0].lastLogin).to.be.a.String();
expect(normalizedData.metadata).to.not.have.property("cached");

Test Skipping Utilities

Helper functions for conditionally skipping tests based on various conditions.

/**
 * Generic type for test definition functions (it, describe, etc.)
 */
type TestDefinition<ARGS extends unknown[], RETVAL> = (
  name: string,
  ...args: ARGS
) => RETVAL;

/**
 * Helper function for skipping tests when a certain condition is met
 * @param skip - Whether to skip the test
 * @param verb - Test function (it, describe) with skip capability
 * @param name - Test name
 * @param args - Additional test arguments
 * @returns Result of calling verb or verb.skip
 */
function skipIf<ARGS extends unknown[], RETVAL>(
  skip: boolean,
  verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},
  name: string,
  ...args: ARGS
): RETVAL;

/**
 * Helper function for skipping tests on Travis CI
 * @param verb - Test function (it, describe) with skip capability
 * @param name - Test name
 * @param args - Additional test arguments
 * @returns Result of calling verb or verb.skip if on Travis
 */
function skipOnTravis<ARGS extends unknown[], RETVAL>(
  verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},
  name: string,
  ...args: ARGS
): RETVAL;

Usage Examples:

import { skipIf, skipOnTravis } from "@loopback/testlab";

// Skip based on feature flags
const features = {
  freeFormProperties: process.env.ENABLE_FREEFORM === "true",
  advancedAuth: process.env.ENABLE_ADVANCED_AUTH === "true",
  experimentalFeatures: process.env.NODE_ENV === "development"
};

// Skip test suite if feature not enabled
skipIf(
  !features.freeFormProperties,
  describe,
  "free-form properties (strict: false)",
  () => {
    it("should allow arbitrary properties", () => {
      // Test implementation
    });

    it("should validate known properties", () => {
      // Test implementation
    });
  }
);

// Skip individual test based on condition
skipIf(
  !features.advancedAuth,
  it,
  "should support multi-factor authentication",
  async () => {
    // Test implementation for MFA
  }
);

// Skip based on environment
skipIf(
  process.platform === "win32",
  it,
  "should handle Unix file permissions",
  () => {
    // Unix-specific test
  }
);

// Skip on Travis CI (for tests that don't work well in CI)
skipOnTravis(it, "should connect to external service", async () => {
  // Test that requires external network access
});

skipOnTravis(describe, "Integration tests with external APIs", () => {
  it("should fetch data from API", async () => {
    // External API test
  });
});

// Complex conditional skipping
const isCI = process.env.CI === "true";
const hasDockerAccess = process.env.DOCKER_AVAILABLE === "true";

skipIf(
  isCI && !hasDockerAccess,
  describe,
  "Docker integration tests",
  () => {
    it("should start container", () => {
      // Docker test
    });
  }
);

// Skip based on Node.js version
const nodeVersion = process.version;
const isOldNode = parseInt(nodeVersion.slice(1)) < 14;

skipIf(
  isOldNode,
  it,
  "should use modern JavaScript features",
  () => {
    // Test using features from Node 14+
  }
);

// Dynamic test generation with conditional skipping
const testCases = [
  {name: "basic", condition: true},
  {name: "advanced", condition: features.advancedAuth},
  {name: "experimental", condition: features.experimentalFeatures}
];

testCases.forEach(testCase => {
  skipIf(
    !testCase.condition,
    it,
    `should handle ${testCase.name} scenario`,
    () => {
      // Test implementation
    }
  );
});

Environment Detection and Helpers

Additional utilities for test environment detection and management.

Usage Examples:

import { skipIf, skipOnTravis } from "@loopback/testlab";

// Environment detection helpers
const isCI = process.env.CI === "true";
const isTravis = process.env.TRAVIS === "true";
const isGitHubActions = process.env.GITHUB_ACTIONS === "true";
const isLocal = !isCI;

// Database availability
const hasDatabase = process.env.DATABASE_URL !== undefined;
const hasRedis = process.env.REDIS_URL !== undefined;

// External service availability
const hasS3Access = process.env.AWS_ACCESS_KEY_ID !== undefined;
const hasEmailService = process.env.SMTP_HOST !== undefined;

// Skip tests based on service availability
skipIf(!hasDatabase, describe, "Database integration tests", () => {
  it("should connect to database", async () => {
    // Database test
  });
});

skipIf(!hasRedis, it, "should cache data in Redis", async () => {
  // Redis test
});

// Skip flaky tests in CI
skipIf(isCI, it, "should handle timing-sensitive operations", async () => {
  // Test that might be flaky in CI due to timing
});

// Skip tests requiring manual setup
skipIf(!hasS3Access, describe, "S3 file upload tests", () => {
  it("should upload file to S3", async () => {
    // S3 upload test
  });
});

// Conditional test suites for different environments
if (isLocal) {
  describe("Local development tests", () => {
    it("should use development configuration", () => {
      // Development-specific tests
    });
  });
}

if (isCI) {
  describe("CI-specific tests", () => {
    it("should use production-like configuration", () => {
      // CI-specific tests
    });
  });
}

// Platform-specific tests
const testsByPlatform = {
  linux: () => {
    it("should handle Linux-specific features", () => {
      // Linux test
    });
  },
  darwin: () => {
    it("should handle macOS-specific features", () => {
      // macOS test
    });
  },
  win32: () => {
    it("should handle Windows-specific features", () => {
      // Windows test
    });
  }
};

const currentPlatform = process.platform as keyof typeof testsByPlatform;
if (testsByPlatform[currentPlatform]) {
  describe(`${currentPlatform} platform tests`, testsByPlatform[currentPlatform]);
}

// Version-based skipping
const nodeVersion = parseInt(process.version.slice(1));
skipIf(nodeVersion < 16, it, "should use Node 16+ features", () => {
  // Modern Node.js features
});

// Memory and performance tests
const hasEnoughMemory = process.env.MEMORY_TEST === "true";
skipIf(!hasEnoughMemory, it, "should handle large datasets", () => {
  // Memory-intensive test
});

// Integration with test frameworks
describe("Conditional test examples", () => {
  // Standard test
  it("should always run", () => {
    expect(true).to.be.true();
  });

  // Conditionally skipped test
  skipIf(
    process.env.SKIP_SLOW_TESTS === "true",
    it,
    "should complete slow operation",
    async function() {
      this.timeout(30000); // 30 second timeout
      // Slow test implementation
    }
  );

  // Travis-specific skip
  skipOnTravis(it, "should work with external dependencies", () => {
    // Test that doesn't work well on Travis
  });
});

docs

assertions.md

http-client.md

http-utilities.md

index.md

request-response-mocking.md

test-doubles.md

test-sandbox.md

validation-helpers.md

tile.json