Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(#zimic): file support improvements #207

Merged
merged 9 commits into from
Jun 3, 2024
99 changes: 65 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Zimic provides a flexible and type-safe way to mock HTTP requests.
- [HTTP `handler.bypass()`](#http-handlerbypass)
- [HTTP `handler.clear()`](#http-handlerclear)
- [HTTP `handler.requests()`](#http-handlerrequests)
- [Intercepted HTTP resources](#intercepted-http-resources)
- [CLI](#cli)
- [`zimic`](#zimic)
- [`zimic browser`](#zimic-browser)
Expand Down Expand Up @@ -738,7 +739,8 @@ const interceptor = http.createInterceptor<Schema>({
```

`onUnhandledRequest` also accepts a function to dynamically choose when to ignore an unhandled request. Calling
`await context.log()` logs the request to the console.
`await context.log()` logs the request to the console. Learn more about the `request` object at
[Intercepted HTTP resources](#intercepted-http-resources).

```ts
import { http } from 'zimic/interceptor';
Expand Down Expand Up @@ -1104,6 +1106,27 @@ const interceptor = http.createInterceptor<{
> types do. Part of Zimic's JSON validation relies on index signatures. To workaround this, you can declare JSON bodies
> using `type`. As an extra step to make sure the type is a valid JSON, you can use the utility type `JSONValue`.

> [!TIP]
>
> The utility type `JSONSerialized`, exported from `zimic`, can be handy to infer the serialized type of an object. It
> converts `Date`'s to strings, removes function properties and serializes nested objects and arrays.

```ts
import { JSONSerialized } from 'zimic';

class User {
name: string;
age: number;
createdAt: Date;
method() {
// ...
}
}

type SerializedUser = JSONSerialized<User>;
// { name: string, age: number, createdAt: string }
```

<details>
<summary>
Declaring a request type with <b>form data</b> body:
Expand Down Expand Up @@ -1913,7 +1936,7 @@ const creationHandler = await interceptor
</details></td></tr></table>

For blob bodies to be correctly parsed, make sure that the intercepted requests have the header `content-type`
indicating a binary data, such as `application/octet-stream`, `image/png`, `audio/mpeg`, etc.
indicating a binary data, such as `application/octet-stream`, `image/png`, `audio/mp3`, etc.

</details>

Expand Down Expand Up @@ -2001,7 +2024,8 @@ const creationHandler = await interceptor

##### Computed restrictions

A function is also supported to declare restrictions in case they are dynamic.
A function is also supported to declare restrictions in case they are dynamic. Learn more about the `request` object at
[Intercepted HTTP resources](#intercepted-http-resources).

<table><tr><td width="900px" valign="top"><details open><summary><b>Local</b></summary>

Expand Down Expand Up @@ -2035,8 +2059,6 @@ const creationHandler = await interceptor

</details></td></tr></table>

The `request` parameter represents the intercepted request, containing useful properties such as `request.body`,
`request.headers`, `request.pathParams`, and `request.searchParams`, which are typed based on the interceptor schema.
The function should return a boolean: `true` if the request matches the handler and should receive the mock response;
`false` otherwise.

Expand Down Expand Up @@ -2219,7 +2241,8 @@ const listHandler = await interceptor.get('/users').respond({

##### Computed responses

A function is also supported to declare a response in case it is dynamic:
A function is also supported to declare a response in case it is dynamic. Learn more about the `request` object at
[Intercepted HTTP resources](#intercepted-http-resources).

<table><tr><td width="900px" valign="top"><details open><summary><b>Local</b></summary>

Expand Down Expand Up @@ -2247,9 +2270,6 @@ const listHandler = await interceptor.get('/users').respond((request) => {

</details></td></tr></table>

The `request` parameter represents the intercepted request, containing useful properties such as `request.body`,
`request.headers`, `request.pathParams`, and `request.searchParams`, which are typed based on the interceptor schema.

#### HTTP `handler.bypass()`

Clears any response declared with [`handler.respond(declaration)`](#http-handlerresponddeclaration), making the handler
Expand Down Expand Up @@ -2353,7 +2373,8 @@ await otherListHandler.requests(); // Now empty
#### HTTP `handler.requests()`

Returns the intercepted requests that matched this handler, along with the responses returned to each of them. This is
useful for testing that the correct requests were made by your application.
useful for testing that the correct requests were made by your application. Learn more about the `request` and
`response` objects at [Intercepted HTTP resources](#intercepted-http-resources).

<table><tr><td width="900px" valign="top"><details open><summary><b>Local</b></summary>

Expand All @@ -2371,10 +2392,12 @@ await fetch(`http://localhost:3000/users/${1}`, {
body: JSON.stringify({ username: 'new' }),
});

const updateRequests = updateHandler.requests();
const updateRequests = await updateHandler.requests();
expect(updateRequests).toHaveLength(1);
expect(updateRequests[0].pathParams).toEqual({ id: '1' });
expect(updateRequests[0].body).toEqual({ username: 'new' });
expect(updateRequests[0].response.status).toBe(200);
expect(updateRequests[0].response.body).toEqual([{ username: 'new' }]);
```

</details></td></tr><tr></tr><tr><td width="900px" valign="top"><details><summary><b>Remote</b></summary>
Expand All @@ -2397,40 +2420,48 @@ const updateRequests = await updateHandler.requests();
expect(updateRequests).toHaveLength(1);
expect(updateRequests[0].pathParams).toEqual({ id: '1' });
expect(updateRequests[0].body).toEqual({ username: 'new' });
expect(updateRequests[0].response.status).toBe(200);
expect(updateRequests[0].response.body).toEqual([{ username: 'new' }]);
```

</details></td></tr></table>

The return by `requests` are simplified objects based on the
[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and
[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) web APIs, containing the body in `request.body`,
typed headers in `request.headers`, typed path params in `request.pathParams`, and typed search params in
`request.searchParams`.
### Intercepted HTTP resources

The body is already parsed based on the header `content-type` of the request or response. JSON objects, form data,
search params, blobs, and plain text are supported. If no `content-type` exists, Zimic tries to parse the body as JSON
and falls back to plain text if it fails.
The intercepted requests and responses are typed based on their [interceptor schema](#declaring-http-service-schemas).
They are available as simplified objects based on the
[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and
[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) web APIs. `body` contains the parsed body, while
typed headers, path params and search params are in `headers`, `pathParams`, and `searchParams`, respectively.

The body is automatically parsed based on the header `content-type` of the request or response. The following table
shows how each type is parsed, where `*` indicates any other resource that does not match the previous types:

| `content-type` | Parsed to |
| ----------------------------------- | --------------------------------------- |
| `application/json` | `JSON` |
| `application/xml` | `String` |
| `application/x-www-form-urlencoded` | [`HttpSearchParams`](#httpsearchparams) |
| `application/*` (others) | `Blob` |
| `multipart/form-data` | [`HttpFormData`](#httpformdata) |
| `multipart/*` (others) | `Blob` |
| `text/*` | `String` |
| `image/*` | `Blob` |
| `audio/*` | `Blob` |
| `font/*` | `Blob` |
| `video/*` | `Blob` |
| `*/*` (others) | `JSON` if possible, otherwise `String` |

If no `content-type` exists or it is unknown, Zimic tries to parse the body as JSON and falls back to plain text if it
fails.

If you need access to the original `Request` and `Response` objects, you can use the `request.raw` property:

<table><tr><td width="900px" valign="top"><details open><summary><b>Local</b></summary>

```ts
const listRequests = listHandler.requests();
console.log(listRequests[0].raw); // Request{}
console.log(listRequests[0].response.raw); // Response{}
console.log(request.raw); // Request{}
console.log(request.response.raw); // Response{}
```

</details></td></tr><tr></tr><tr><td width="900px" valign="top"><details><summary><b>Remote</b></summary>

```ts
const listRequests = await listHandler.requests();
console.log(listRequests[0].raw); // Request{}
console.log(listRequests[0].response.raw); // Response{}
```

</details></td></tr></table>

## CLI

### `zimic`
Expand Down
Loading
Loading