4 ms·
"And you shouldn't cast objects in almost no code ever." - I have a question about tests. Imagine I want to test a function that operates on quite large applica
by veidelis 4y ago
"And you shouldn't cast objects in almost no code ever." - I have a question about tests. Imagine I want to test a function that operates on quite large application state but not all app state is necessary for that function.
Options:
- Define all app state as a snapshot. Problem: snapshot can become stale, so more infra might be necessary to make sure that snapshot is up to date;
- Pass only the necessary state and construct as necessary. Problem: hard to define whole state precisely and ensure that it conforms to runtime state of a healthy app;
- Pass a subset of necessary state for some execution branch and cast the type. Problem: casting may result in test failures during runtime and potentially other issues such as modify-run-fail debug loop;
- Mock return values of functions called within the function being tested and use any combination of "state passing options above".
In a lot of places I use such approach with custom type helpers and transitive types, and passing in only the necessary subset for smaller functions or mocking return values for bigger ones. What do you think? I know that the AppState can be defined as a union of possible states and together with type guards can address those issues better. I just wanted to hear your opinion on how you would address such problems. I hope I explained it well enough.
export type Fn = (...params: any) => any;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
export type FirstParamType<G> = G extends Fn[]
? UnionToIntersection<Parameters<G[number]>[0]>
: G extends Fn
? Parameters<G>[0]
: never;
export interface AppState {
first: {
a: number;
b: number[];
};
second: {
c: string;
d: string[];
}
}
type DeepPick<A, B extends keyof A, C extends keyof A[B]> = { [BK in B]: Pick<A[B], C> };
function calculateUsingFirstB(state: DeepPick<AppState, "first", "b">): number[] {
return state.first.b; // some calculation
}
function calculateUsingSecondC(state: DeepPick<AppState, "second", "c">): string {
return state.second.c; // another calculation
}
// function which takes complex state parameter and calculates the result based on results of other functions
function calculateMore(state: FirstParamType<[typeof calculateUsingFirstB, typeof calculateUsingSecondC]> & DeepPick<AppState, "first", "a">): string | number[] {
if (state.first.a > 10) {
return calculateUsingFirstB(state);
}
return calculateUsingSecondC(state);
}
- hither_shores 4y ago> Imagine I want to test a function that operates on quite large application state but not all app state is necessary for that function If not all app state is necessary for that function, it shouldn't require the whole app state in its arguments. type AppState = { databaseConnection: DatabaseConnection, env : "dev" | "prod", apiToken: string, userId: string, ... } & SomeOtherStuff const dropTables = (app: Pick<AppState, "databaseConnection"> & {env: "dev"} ) => app.databaseConnection.dropTables() Conversely, if a function you don't control says it needs the whole app state, believe it.