> For AI agents: the complete documentation index is available at https://rslint.rs/llms.txt, the full documentation bundle is available at https://rslint.rs/llms-full.txt.

# require-mock-type-parameters

[Added in v0.9.1](https://github.com/web-infra-dev/rslint/releases/tag/v0.9.1)
## Configuration

| Preset                     | Configured Value |
| -------------------------- | ---------------- |
| ✅ rstestPlugin.configs.all | `"warn"`         |


```ts title=rslint.config.ts
import { defineConfig, rstestPlugin } from '@rslint/core';

export default defineConfig([
  rstestPlugin.configs.recommended,
  {
    rules: {
      'rstest/require-mock-type-parameters': 'error',
    },
  },
]);
```

## Rule Details

Requires `rs.fn()` to be given a type parameter. With nothing passed for the compiler to infer a signature from, the mock falls back to one that accepts any arguments and returns `any`, so nothing about how the test drives it is checked: a call with the wrong arguments, a `mockResolvedValue` of the wrong shape, and an assertion against a property the real function never returns all pass the compiler. Writing the signature — `rs.fn<(id: number) => Promise<User>>()` — makes the mock stand in for the function it replaces, in the editor as well as at build time.

A call that passes an implementation is reported too, even though the compiler does infer a signature from it: `rs.fn((value: number) => value.toString())` is a `Mock<(value: number) => string>`. That signature describes the throwaway body the test happened to write rather than the function the mock stands in for, so it moves whenever the body does — relaxing a parameter to `unknown`, or writing the `() => {}` a stub usually is, quietly relaxes what every later argument list, `mockReturnValue` and assertion is checked against, and the second of those checks nothing at all. The type parameter states the contract once, and the implementation is then checked against it as well.

`fn` is an ordinary function on the utilities object, so the rule follows the binding: it is reported whether the receiver came from an `import` of `@rstest/core` or `rstack/test` under either of its names, a `require` of either, a further rename such as `import { rs as mocker }`, a namespace as in `core.rs.fn()`, or the [globals](https://rstest.rs/config/test/globals) Rstest installs. A receiver the file declares itself — a local object, a function parameter, a binding imported from somewhere else — is a different object and is left alone. The call has to go through a plain dotted member, so `rs['fn']()` is not reported, and `rs.fn` read as a value without being called carries no call to write a type parameter on.

With `checkImportFunctions` enabled the rule also covers the four APIs that load a module: `importActual`, `importMock`, `requireActual` and `requireMock`. Each returns `Record<string, unknown>` unless told what the module exports, so the result has no named exports and nothing callable on it. These four are rewritten by Rstest's build rather than called, and the build matches the receiver on the name written at the call site: it must be spelled `rs` or `rstest`, so a renamed binding and a namespace are left as they are, because Rstest leaves them alone too and they throw where they stand. An optional receiver, `rs?.importActual('./dep')`, is never rewritten and never reported.

How the member itself may be written differs between the two pairs, because the build reads them differently. `importActual` and `requireActual` are also rewritten through a bracketed string key or an optional call, so `rs['importActual']('./dep')` and `rs.importActual?.('./dep')` are reported alongside `rs.importActual('./dep')`; and a receiver the file declares itself — a local object, a function parameter — takes the call back, so it is not reported. `importMock` and `requireMock` are rewritten only as a plain dotted member: `rs['importMock']('./dep')` and `rs.importMock?.('./dep')` throw where they stand and are not reported, while a receiver the file declares itself does not stop the rewrite, so `rs.importMock('./dep')` is still reported there.

A call the build cannot rewrite is never reported: a path that is not a quoted string, or any argument count other than one.

Parentheses and TypeScript's type-only syntax are transparent throughout: `(rs as any).fn()`, `rs!.fn()` and `(rs.fn)()` are reported exactly like the bare form. A call that already carries a type argument is what the rule asks for and is never reported.

## Incorrect

```ts
import { expect, rs, test } from '@rstest/core';
import { checkout } from './checkout';

test('charges the card once', async () => {
  const charge = rs.fn();

  await checkout({ items: [{ sku: 'A1', price: 10 }] }, charge);

  expect(charge).toHaveBeenCalledExactlyOnceWith(10);
});
```

## Correct

```ts
import { expect, rs, test } from '@rstest/core';
import { checkout } from './checkout';

test('charges the card once', async () => {
  const charge = rs.fn<(amount: number) => Promise<void>>();

  await checkout({ items: [{ sku: 'A1', price: 10 }] }, charge);

  expect(charge).toHaveBeenCalledExactlyOnceWith(10);
});
```

## Options

```json
{
  "rstest/require-mock-type-parameters": [
    "error",
    {
      "checkImportFunctions": true
    }
  ]
}
```

| Option                 | Type      | Default | Description                                                                                       |
| ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------- |
| `checkImportFunctions` | `boolean` | `false` | Also require type parameters for `importActual`, `importMock`, `requireActual` and `requireMock`. |
