我有一个 Lit 组件,它对外部 API 进行两次 AJAX 调用:
1
PUT /api/rest/books/
{ view: "VIEW_1", properties: { a: "abcde" } }
mockResponse1
2
PUT /api/rest/books/
{ view: "VIEW_2", properties: { b: "1234" } }
mockResponse2
然后,在 Storybook 中,我使用 @web/mocks,它在幕后使用 MSW:
import { registerMockRoutes } from '@web/mocks/browser.js';
import { http } from '@web/mocks/http.js';
...
export const main = () => html`<my-dummy-component></my-dummy-component>`;
main.parameters = {
...
mocks: [
http.put('/api/rest/books/', () => Response.json(mockResponse1)),
http.put('/api/rest/books/', () => Response.json(mockResponse2)) // <--- How to differentiate this call?
]
};
正如预期的那样,因为 HTTP 方法和端点相同,所以两个 API 调用返回相同的
mockResponse1
,但这不是我想要的。
我尝试了以下解决方案:创建一个请求处理程序来检查请求正文中的特定属性(例如,唯一标识符):
mocks: [
http.put('/api/rest/books', async ({request}) => {
const req = await request.json()
if(req.requestId === 1) return Response.json(mockResponse1)
if(req.requestId === 2) return Response.json(mockResponse2)
})
]
问题:这可行,但我将向真实服务器发送一个仅用于测试的属性。
想法 1: 为我的 API 服务实现一个 HTTP 拦截器,用于查找自定义环境变量:
isDev
为true(表示开发或测试环境),则在payload中添加callId。isDev
为 false(生产版本),则省略 requestId
。想法 2: 不要修改有效负载,而是观察我知道将始终存在的属性。例如,如果请求 1 在其有效负载中始终包含属性视图:“VIEW_1”,则将其用作模拟处理程序的基础。然而,我与有效负载紧密耦合。如果将来请求 1 不再需要发送
view: "VIEW_1"
怎么办?
您对这些方法有何看法?
最后我采用了第二种策略。可以分两步完成。见下图:
首先,看一下项目的结构:
+---mocks
| | index.js <---- HERE is all the logic for OUR handlers
| |
| \---responses
| mock-response-1.js <---- Just a random JSON
| mock-response-2.js
|
\---src
\---components
\---dumb-component
DumbComponent.js
DumbComponent.stories.js <---- HERE we tell Storybook to use our mocks
第1步。实现区分处理程序的逻辑。在 mocks/index.js:
import { http } from '@web/mocks/http.js';
import mockResponse1 from './responses/mock-response-1.js';
import mockResponse2 from './responses/mock-response-2.js';
function __createHandler(conditionsAndResponses) {
return [
http.put('/api/rest/books/', async ({ request }) => {
const payload = await request.json();
for (const { check, response } of conditionsAndResponses) {
if (check(payload)) { // Check if the payload meets our requirements
return Response.json(response); // Return appropriate response
}
}
}),
];
}
const __dumbCompHandler = __createHandler([
{
check: ({ view }) => view === 'VIEW_1', // The handler will run this check looking for the property that we know will always be present in the payload
response: mockResponse1, // If the check successes, we want the handler to return this response
},
{
check: ({ view }) => view === 'VIEW_2',
response: mockResponse2,
},
]);
export default {
dumbComp: __dumbCompHandler(),
};
第 2 步。 告诉 Storybook 模拟从我们的组件发出的 HTTP 调用。在 DumbComponent.stories.js:
import { html } from 'ing-web-es/lit-2.js';
import { DumbComponent } from './src/DumbComponent.js';
import mocks from '../../mocks/index.js';
customElements.define('dumb-component', DumbComponent);
export default {
title: 'DumbComponent',
};
export const main= () => html`<dumb-component></dumb-component>`;
main.parameters = {
mocks: [mocks.newPinSetup], // <---- THIS LINE DOES EVERYTHING
};
奖金
如果我想根据某个变量的值返回另一个响应怎么办?例如,如果
mockErrorResponse
,我想返回 isLibraryClosed = true
。
在 mocks/index.js:
function __createDumbCompHandler({ isLibraryClosed = false }) {
return __createHandler([
{
check: ({ view }) => view === 'VIEW_1',
response: isLibraryClosed ? mockErrorResponse : mockResponse1, // Your logic here
},
{
check: ({ view }) => view === 'VIEW_2',
response: mockResponse2,
},
]);
}
export default {
dumbComp: (config) => __createDumbCompHandler(config),
};
在 DumbComponent.stories.js:
main.parameters = {
mocks: [mocks.newPinSetup({ isLibraryClosed: true })], <---- THIS LINE DOES EVERYTHING
};
就这些了!