Skip to content

Commit

Permalink
Add support for configuring k6-jslib-testing via the environment (#9)
Browse files Browse the repository at this point in the history
Introduces support for the `K6_TESTING_COLORIZE`, `K6_TESTING_DISPLAY`,
`K6_TESTING_TIMEOUT` and `K6_TESTING_INTERVAL` environment variables to
configure the expect keyword accordingly. Precedence, from higher to
lower is env > js `configure` call > defaults.

See README for documentation.
  • Loading branch information
oleiade authored Feb 4, 2025
1 parent d611e24 commit 34ecbb6
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 63 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,23 @@ You can create a new expect instance with the `.configure` method. This will
allow you to configure the behavior of the assertions. The configuration is
applied to all assertions made using the expect instance.
##### Available configuration options
The available configuration options are:
| Option | Default | Environment variable | Description |
| ---------- | -------- | --------------------- | -------------------------------------------------------------------------------------- |
| `colorize` | true | `K6_TESTING_COLORIZE` | Whether to colorize the output of the expect function. |
| `display` | "pretty" | `K6_TESTING_DISPLAY` | The display format to use. "pretty" (default) or "inline". |
| `timeout` | 5000 | `K6_TESTING_TIMEOUT` | Specific to retrying assertions. The timeout for assertions, in milliseconds. |
| `interval` | 100 | `K6_TESTING_INTERVAL` | Specific to retrying assertions. The polling interval for assertions, in milliseconds. |
##### Example with inline display and no colorization
```javascript
export default function () {
// Create a new expect instance with the default configuration
const myExpect = new expect().configure({
const myExpect = expect.configure({
// Display assertions using an inline format, aimed towards making them more readable in logs
display: "inline",
Expand Down Expand Up @@ -169,17 +180,6 @@ export default function () {
}
```
##### Available configuration options
The available configuration options are:
| Option | Default | Description |
| ---------- | -------- | -------------------------------------------------------------------------------------- |
| `colorize` | true | Whether to colorize the output of the expect function. |
| `display` | "pretty" | The display format to use. "pretty" (default) or "inline". |
| `timeout` | 5000 | Specific to retrying assertions. The timeout for assertions, in milliseconds. |
| `interval` | 100 | Specific to retrying assertions. The polling interval for assertions, in milliseconds. |
## Examples
### API Testing
Expand Down
96 changes: 96 additions & 0 deletions config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { assertEquals } from "jsr:@std/assert";

import { ConfigLoader } from "./config.ts";
import { withEnv } from "./test_helpers.ts";

Deno.test("ConfigLoader.load", async (t) => {
await t.step("colorize defaults to true", () => {
const config = ConfigLoader.load();
assertEquals(config.colorize, true);
});

await t.step(
"colorize can be enforced by setting the K6_TESTING_COLORIZE environment variable to true",
() => {
withEnv("K6_TESTING_COLORIZE", "true", () => {
const config = ConfigLoader.load();
assertEquals(config.colorize, true);
});
},
);

await t.step(
"colorize can be enforced by setting the K6_TESTING_COLORIZE environment variable",
() => {
withEnv("K6_TESTING_COLORIZE", "", () => {
const config = ConfigLoader.load();
assertEquals(config.colorize, true);
});
},
);

await t.step(
"colorize can be disabled by setting the K6_TESTING_COLORIZE environment variable to false",
() => {
withEnv("K6_TESTING_COLORIZE", "false", () => {
const config = ConfigLoader.load();
assertEquals(config.colorize, false);
});
},
);

await t.step(
"colorize is enabled if K6_TESTING_COLORIZE is set to any other value than false",
() => {
withEnv("K6_TESTING_COLORIZE", "foo", () => {
const config = ConfigLoader.load();
assertEquals(config.colorize, true);
});
},
);

await t.step("display defaults to pretty", () => {
const config = ConfigLoader.load();
assertEquals(config.display, "pretty");
});

await t.step(
"display can be set to inline by setting the K6_TESTING_DISPLAY environment variable to inline",
() => {
withEnv("K6_TESTING_DISPLAY", "inline", () => {
const config = ConfigLoader.load();
assertEquals(config.display, "inline");
});
},
);

await t.step("timeout defaults to 5000", () => {
const config = ConfigLoader.load();
assertEquals(config.timeout, 5000);
});

await t.step(
"timeout can be set by setting the K6_TESTING_TIMEOUT environment variable",
() => {
withEnv("K6_TESTING_TIMEOUT", "10000", () => {
const config = ConfigLoader.load();
assertEquals(config.timeout, 10000);
});
},
);

await t.step("interval defaults to 100", () => {
const config = ConfigLoader.load();
assertEquals(config.interval, 100);
});

await t.step(
"interval can be set by setting the K6_TESTING_INTERVAL environment variable",
() => {
withEnv("K6_TESTING_INTERVAL", "200", () => {
const config = ConfigLoader.load();
assertEquals(config.interval, 200);
});
},
);
});
85 changes: 67 additions & 18 deletions config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert } from "./assert.ts";
import { env } from "./environment.ts";
import { envParser } from "./environment.ts";

/**
* Options that can be set for the expect function.
Expand All @@ -20,23 +20,6 @@ export interface ExpectConfig extends RenderConfig, RetryConfig {
assertFn?: (...args: Parameters<typeof import("./assert.ts").assert>) => void;
}

/**
* Creates a default configuration for the expect function.
*
* @returns the default configuration
*/
export function makeDefaultConfig(): ExpectConfig {
const noColor = env.NO_COLOR !== undefined;

return {
...DEFAULT_RETRY_OPTIONS,
soft: false,
display: "pretty",
colorize: !noColor,
assertFn: assert,
};
}

/**
* The configuration for the retry behavior.
*/
Expand Down Expand Up @@ -91,3 +74,69 @@ export interface RenderConfig {
* "inline" is a logfmt style format that outputs in a single line.
*/
export type DisplayFormat = "inline" | "pretty";

/**
* Default configuration values, without any environment overrides
*/
export const DEFAULT_CONFIG: ExpectConfig = {
...DEFAULT_RETRY_OPTIONS,
soft: false,
colorize: true,
display: "pretty",
assertFn: assert,
};

/**
* Configuration loader that handles different sources of configuration
* with clear precedence rules
*/
export class ConfigLoader {
/**
* Loads configuration with the following precedence (highest to lowest):
* 1. Environment variables
* 2. Explicit configuration passed to the function
* 3. Default values
*/
static load(explicitConfig: Partial<ExpectConfig> = {}): ExpectConfig {
const envConfig = ConfigLoader.loadFromEnv();

return {
...DEFAULT_CONFIG,
...explicitConfig,
...envConfig,
};
}

/**
* Loads configuration from environment variables
* Returns only the values that are explicitly set in the environment
*/
private static loadFromEnv(): Partial<ExpectConfig> {
const config: Partial<ExpectConfig> = {};

// Load colorize from environment variable
if (envParser.hasValue("K6_TESTING_COLORIZE")) {
config.colorize = envParser.boolean("K6_TESTING_COLORIZE");
}

// Load display from environment variable
if (envParser.hasValue("K6_TESTING_DISPLAY")) {
config.display = envParser.enum<DisplayFormat>(
"K6_TESTING_DISPLAY",
["inline", "pretty"],
);
}

// Load timeout from environment variable
if (envParser.hasValue("K6_TESTING_TIMEOUT")) {
config.timeout = envParser.number("K6_TESTING_TIMEOUT");
}

// Load interval from environment variable
if (envParser.hasValue("K6_TESTING_INTERVAL")) {
config.interval = envParser.number("K6_TESTING_INTERVAL");
}

return config;
}
}
71 changes: 71 additions & 0 deletions environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,74 @@ function getEnvironment(): Environment {

// Export a singleton instance of the environment object
export const env: Environment = getEnvironment();

/**
* Environment variable parser
*/
export const envParser = {
/**
* Check if an environment variable is set
*/
hasValue(key: string): boolean {
return env[key] !== undefined;
},

/**
* Parse a boolean environment variable
* "false" (case insensitive) -> false
* anything else -> true
* @throws if value is undefined
*/
boolean(key: string): boolean {
const value = env[key]?.toLowerCase();
if (value === undefined) {
throw new Error(`Environment variable ${key} is not set`);
}
return value !== "false";
},

/**
* Parse an environment variable that should match specific values
* @throws if value is undefined or doesn't match allowed values
*/
enum<T extends string>(key: string, allowedValues: T[]): T {
const value = env[key]?.toLowerCase() as T;
if (value === undefined) {
throw new Error(`Environment variable ${key} is not set`);
}
if (!allowedValues.includes(value)) {
throw new Error(
`Invalid value for ${key}. Must be one of: ${allowedValues.join(", ")}`,
);
}
return value;
},

/**
* Parses an environment variable as a non-negative number.
* @param name The name of the environment variable
* @throws Error if the value is not a valid non-negative number
* @returns The parsed number value
*/
number(name: string): number {
const value = env[name];
if (!value) {
throw new Error(`Environment variable ${name} is not set`);
}

const parsed = Number(value);
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
throw new Error(
`Environment variable ${name} must be a valid number, got: ${value}`,
);
}

if (parsed < 0) {
throw new Error(
`Environment variable ${name} must be a non-negative number, got: ${value}`,
);
}

return parsed;
},
};
42 changes: 29 additions & 13 deletions expect.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { assertEquals } from "jsr:@std/assert";

import { expect } from "./expect.ts";
import { env } from "./environment.ts";
import { withEnv } from "./test_helpers.ts";

Deno.test("expect.configure", async (t) => {
await t.step(
"NO_COLOR environment variable should have priority over colorize option",
"K6_TESTING_COLORIZE environment variable should have priority over colorize option",
() => {
withEnv("NO_COLOR", "true", () => {
withEnv("K6_TESTING_COLORIZE", "false", () => {
const ex = expect.configure({
colorize: true,
});
Expand All @@ -18,22 +18,38 @@ Deno.test("expect.configure", async (t) => {
);

await t.step(
"When NO_COLOR is not set, colorize option should have priority over NO_COLOR environment variable",
"K6_TESTING_COLORIZE not set, colorize option should be respected",
() => {
// Assuming NO_COLOR is not set in the environment, the colorize option should be the source of truth
// Assuming K6_TESTING_COLORIZE is not set in the environment, the colorize option should be the source of truth
const ex = expect.configure({
colorize: true,
});

assertEquals(ex.config.colorize, true);
},
);
});

// Helper function to set an environment variable for the duration of a test
function withEnv(key: string, value: string, fn: () => void) {
const originalValue = env[key];
env[key] = value;
fn();
env[key] = originalValue;
}
await t.step(
"K6_TESTING_DISPLAY environment variable should have priority over display option",
() => {
withEnv("K6_TESTING_DISPLAY", "inline", () => {
const ex = expect.configure({
display: "pretty",
});

assertEquals(ex.config.display, "inline");
});
},
);

await t.step(
"K6_TESTING_DISPLAY not set, display option should be respected",
() => {
const ex = expect.configure({
display: "pretty",
});

assertEquals(ex.config.display, "pretty");
},
);
});
Loading

0 comments on commit 34ecbb6

Please sign in to comment.