UNPKG

@boostercloud/application-tester

Version:

Contains Booster types related to the information extracted from the user project

94 lines (93 loc) 3.88 kB
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 {};