- commit
- a360879
- parent
- 3f5d0aa
- author
- Eric Bower
- date
- 2023-12-19 01:46:16 +0000 UTC
docs: readme
1 files changed,
+92,
-0
+92,
-0
1@@ -110,8 +110,22 @@ api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
2 // automatically cache Response json in datastore as-is
3 export const fetchUsers = api.get("/users", api.cache());
4
5+// create a POST HTTP request
6+export const updateUser = api.post<{ id: string; name: string }>(
7+ "/users/:id",
8+ function* (ctx, next) {
9+ ctx.request = ctx.req({
10+ body: JSON.stringify({ name: ctx.payload.name }),
11+ });
12+ yield* next();
13+ },
14+);
15+
16 store.dispatch(fetchUsers());
17 // now accessible with useCache(fetchUsers)
18+
19+// lets update a user record
20+store.dispatch(updateUser({ id: "1", name: "bobby" }));
21 ```
22
23 # example: an immutable store that acts like a reactive, in-memory database
24@@ -346,6 +360,84 @@ or a user-defined supervisor.
25 That's it. We are just leveraging the same tiny API that we are already using in
26 `starfx`.
27
28+# example: test that doesn't need an http interceptor
29+
30+Need to write tests? Users to libraries like `msw` or `nock`? Well you don't
31+need them with `starfx`. If the `mdw.fetch()` middleware detects `ctx.response`
32+is already filled then it skips making the request. Let's take the update user
33+endpoint example and provide stubbed data for our tests.
34+
35+```ts
36+import { fireEvent, render, screen } from "@testing-library/react";
37+import { useDispatch, useSelector } from "starfx/react";
38+import { db } from "./schema.ts";
39+import { updateUser } from "./user.ts";
40+
41+function UserSettingsPage() {
42+ const id = "1";
43+ const dispatch = useDispatch();
44+ const user = useSelector((state) => db.users.selectById(state, { id }));
45+
46+ return (
47+ <div>
48+ <div>Name: {user.name}</div>
49+ <button onClick={() => dispatch(updateUser({ id, name: "bobby" }))}>
50+ Update User
51+ </button>
52+ </div>
53+ );
54+}
55+
56+describe("UserSettingsPage", () => {
57+ it("should update the user", async () => {
58+ // just for this test -- inject a new middleware into the endpoint stack
59+ updateUser.use(function* (ctx, next) {
60+ ctx.response = new Response(
61+ JSON.stringify({ id: ctx.payload.id, name: ctx.payload.name }),
62+ );
63+ yield* next();
64+ });
65+
66+ render(<UserSettingsPage />);
67+
68+ const btn = await screen.findByRole("button", { name: /Update User/ });
69+ fireEvent.click(btn);
70+
71+ await screen.findByText(/Name: bobby/);
72+ });
73+});
74+```
75+
76+That's it. No need for http interceptors and the core functionality works
77+exactly the same, we just skip making the fetch request for our tests.
78+
79+What if we don't have an API endpoint yet and want to stub the data? We use the
80+same concept but inline inside the `updateUser` endpoint:
81+
82+```ts
83+export const updateUser = api.post<{ id: string; name: string }>(
84+ "/users/:id",
85+ [
86+ function* (ctx, next) {
87+ ctx.request = ctx.req({
88+ body: JSON.stringify({ name: ctx.payload.name }),
89+ });
90+ yield* next();
91+ },
92+ function* (ctx, next) {
93+ ctx.response = new Response(
94+ JSON.stringify({ id: ctx.payload.id, name: ctx.payload.name }),
95+ );
96+ yield* next();
97+ },
98+ ],
99+);
100+```
101+
102+Wow! Our stubbed data is now colocated next to our actual endpoint we are trying
103+to mock! Once we have a real API we want to hit, we can just remove that second
104+middleware function and everything will work exactly the same.
105+
106 # talk
107
108 I recently gave a talk about delimited continuations where I also discuss this