@boostercloud/application-tester
Version:
Contains Booster types related to the information extracted from the user project
94 lines (93 loc) • 3.88 kB
TypeScript
import { Effect, Has, Layer, Tag } from '@boostercloud/framework-types/dist/effect';
import { ShapeFn } from '@effect-ts/core/Effect';
import { SinonSpy } from 'sinon';
/**
* Main entry point of the Effect testing helper module.
* You pass the tag for your service, and a record of mocks for the functions. This function will
* return a helper object that contains a `Layer` that you can use to replace the service in your
* tests, the fakes record that you passed, and a `reset` function that will reset the history
* of all the fakes, so you can use them in multiple tests.
*
* A useful pattern is to create a `makeTestService` function that will create a new instance of
* the service for each test, passing the overrides you need for that test. This way, you can
* use the same `makeTestService` function in multiple tests, and you don't need to be passing all
* the fakes in every test.
*
* NOTE: This has a limitation where the service cannot define constants or values that are effects,
* as a workaround, you can define them as functions that return the value.
*
* E.g.:
* `() => Effect<...>` instead of `Effect<...>`
*
* and
*
* `() => Effect<..., T>` instead of `T`
*
* @example
* Here's the typical usage of this function (you can also check the tests in the @boostercloud/cli package):
* ```typescript
* import { fakeService, FakeOverrides } from '@boostercloud/application-tester/src/effect'
* import { fake } from 'sinon'
* import { MyService } from '../../../src/services/my-service'
*
* const makeTestMyService = (overrides?: FakeOverrides<MyService>) =>
* fakeService(MyService, {
* voidMethodInService: fake(),
* methodThatReturnsAValue: fake.returns(''),
* ...overrides,
* })
*
* // In your test:
* describe('my test', () => {
* it('should do something', async () => {
* const { layer, fakes, reset } = makeTestMyService()
*
* await unsafeRunEffect(functionThatUsesMyService(), {
* layer,
* onError: orDie
* })
*
* expect(fakes.methodThatReturnsAValue).to.have.been.calledWith('some value')
* reset() // You can also reset the fakes in an `afterEach` hook
* })
* })
* ```
*
* @param tag - The tag of the service to fake. E.g. `ProcessService`.
* @param fakes - A record of the methods to fake. E.g. `{ cwd: fake.returns(''), exec: fake.returns('') }`.
* @return {FakeServiceUtils} - An object with the layer to use in the dependency graph, and the fakes to assert the service was called with the right parameters.
*/
export declare const fakeService: <T extends ShapeFn<T>>(tag: Tag<T>, fakes: Fakes<T>) => FakeServiceUtils<T>;
/**
* Utils to mock an entire service, without having to wire up the whole dependency graph.
* This is useful for unit testing, but not for integration testing.
*
* @typedef {Object} FakeServiceUtils
* @property {Layer} layer - The layer that can be used to replace the service in the dependency graph
* @property {Record<string, SinonSpy>} fakes - The fakes that can be used to assert the service was called with the right parameters
* @property {() => void} reset - A function to reset all the fakes
*/
export type FakeServiceUtils<T extends ShapeFn<T>> = {
layer: Layer.Layer<unknown, never, Has<T>>;
fakes: Fakes<T>;
reset: () => void;
};
/**
* Gets the result type from an Effect
*/
type EffectResult<T> = T extends Effect<any, any, infer A> ? A : never;
/**
* Type to pass fakes on the creation of a fake service.
*/
type Fakes<T extends ShapeFn<T>> = {
[key in keyof T]: EffectSpy<T, key>;
};
/**
* Allows overriding fakes in test service generators
*/
export type FakeOverrides<T extends ShapeFn<T>> = Partial<Fakes<T>>;
/**
* Spy for a specific service function
*/
type EffectSpy<T extends ShapeFn<T>, key extends keyof T> = SinonSpy<any[], EffectResult<ReturnType<T[key]>>>;
export {};