Skip to content

Commit 26e9e9e

Browse files
committed
Add TestEnv class to simplify testing of ActionState-dependent functions
1 parent 9b35ab9 commit 26e9e9e

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

src/testing-utils.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import test, {
1010
import nock from "nock";
1111
import * as sinon from "sinon";
1212

13+
import { ActionState, StateFeature } from "./action-common";
1314
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
1415
import { AnalysisKind } from "./analyses";
1516
import * as apiClient from "./api-client";
@@ -188,6 +189,99 @@ export function getTestActionsEnv(): ActionsEnv {
188189
};
189190
}
190191

192+
/** For testing purposes, we make all available state features accessible in `TestEnv`. */
193+
type AllState = ["Logger", "Env", "FeatureFlags"];
194+
195+
/**
196+
* Wraps a function that accepts an `ActionState` for testing in different environments.
197+
*/
198+
export class TestEnv<
199+
Args extends readonly any[],
200+
R,
201+
Fs extends ReadonlyArray<AllState[number]>,
202+
> {
203+
private readonly fn: (state: ActionState<Fs>, ...args: Args) => R;
204+
private args?: Args;
205+
private logger: RecordingLogger;
206+
private state: ActionState<AllState>;
207+
208+
constructor(
209+
fn: (state: ActionState<Fs>, ...args: Args) => R,
210+
cloneFrom?: TestEnv<Args, R, Fs>,
211+
) {
212+
this.fn = fn;
213+
this.args = cloneFrom?.args;
214+
this.logger = new RecordingLogger();
215+
this.state =
216+
cloneFrom !== undefined
217+
? { ...cloneFrom.state }
218+
: {
219+
logger: new RecordingLogger(),
220+
env: getTestEnv(),
221+
features: createFeatures([]),
222+
};
223+
}
224+
225+
private clone(): TestEnv<Args, R, Fs> {
226+
return new TestEnv(this.fn, this);
227+
}
228+
229+
public getLogger(): RecordingLogger {
230+
return this.logger;
231+
}
232+
233+
public getState(): ActionState<AllState> {
234+
return this.state;
235+
}
236+
237+
public getArgs(): Args | undefined {
238+
return this.args;
239+
}
240+
241+
public withArgs(...args: Args) {
242+
const result = this.clone();
243+
result.args = args;
244+
return result;
245+
}
246+
247+
public withFeatures(enabled: Feature[]): TestEnv<Args, R, Fs> {
248+
const result = this.clone();
249+
result.state.features = createFeatures(enabled);
250+
return result;
251+
}
252+
253+
public withEnv(env: Env): TestEnv<Args, R, Fs> {
254+
const result = this.clone();
255+
result.state.env = env;
256+
return result;
257+
}
258+
259+
call(): R {
260+
if (!this.args) {
261+
throw new Error("Trying to call function in TestEnv without arguments.");
262+
}
263+
return this.fn(this.state as ActionState<Fs>, ...this.args);
264+
}
265+
266+
public passes<T>(
267+
assertion: (makeCall: () => R) => T | Promise<T>,
268+
): T | Promise<T> {
269+
return assertion(() => {
270+
const result = this.call();
271+
return result;
272+
});
273+
}
274+
}
275+
276+
/** Utility function to construct a `TestEnv`. */
277+
export function callee<
278+
Args extends readonly any[],
279+
R,
280+
Fs extends readonly StateFeature[],
281+
>(fn: (state: ActionState<Fs>, ...args: Args) => R): TestEnv<Args, R, Fs> {
282+
return new TestEnv(fn);
283+
}
284+
191285
/**
192286
* Default values for environment variables typically set in an Actions
193287
* environment. Tests can override individual variables by passing them in the

0 commit comments

Comments
 (0)