Should a mocked m.request reject on non-2xx responses, or is that the unwrapError hook's job?
0 reputation · 08 Jul 2025, 10:48 UTC
0 reputation · 08 Jul 2025, 10:48 UTC
I'm writing integration tests for a Mithril component that fetches JSON via m.request, and I want to avoid any real network calls or production credentials. My plan is to stub the underlying XMLHttpRequest (or supply a custom function through m.request's config) so the test resolves with a canned payload.
The part I can't settle is error handling. When my mock returns a 4xx or 5xx status, it's unclear whether m.request itself is expected to reject the promise, or whether rejection only happens if I define an unwrapError hook. This matters because my component's error path is triggered from the promise's rejection, and I need the mock to faithfully reproduce production behavior. Different examples I've seen seem to assume different conventions, which makes me unsure my tests reflect reality.
Assume Mithril 2.x with Jest and Sinon for stubbing.
Does m.request reject automatically on non-2xx statuses, or does that depend entirely on unwrapError? When stubbing XHR for tests, which of these behaviors should the mock emulate to match a real server? Is there a documented, preferred way to simulate failure responses without a live backend?
31775 reputation · 08 Jul 2025, 18:51 UTC
In Mithril 2.x, m.request never rejects the returned promise for HTTP status codes outside the 2xx range unless the caller supplies an unwrapError hook that throws. The hook is the only mechanism that turns a non‑2xx response into a rejected promise.
m.request resolves with an object containing status, code, and the parsed body.unwrapError option is optional; when provided, it receives the same object and may throw to cause rejection.unwrapError is omitted, the promise will always resolve, regardless of status.xhr function via config.xhr bypasses the default XHR handling but does not change the resolve/reject semantics.Many snippets from older Mithril 1.x or community examples assume automatic rejection on 4xx/5xx. In 2.x that behavior is opt‑in. Therefore, when writing tests you should emulate the real server by returning a normal XHR response (status 4xx/5xx) and let the component decide whether to treat it as an error via unwrapError or by inspecting the status field.
Below is a minimal, scoped example that works with Jest and Sinon. It shows how to create a fake XHR, set the status to 404, and trigger the ready‑state change so that m.request resolves with the error payload.
// test/myComponent.test.js
import m from 'mithril';
import sinon from 'sinon';
// 1. Create a fake XHR that mimics the real one
const fakeXHR = sinon.useFakeXMLHttpRequest();
let requests = [];
fakeXHR.onCreate = function (xhr) {
requests.push(xhr);
// Store the send callback for later
xhr.send = function (body) {
// Simulate async network delay
setTimeout(() => {
xhr.status = 404; // non‑2xx status
xhr.responseText = JSON.stringify({error: 'Not found'});
xhr.readyState = 4;
xhr.onreadystatechange();
}, 0);
};
};
// 2. Call the component’s m.request (no unwrapError)
const promise = m.request({
method: 'GET',
url: '/api/resource',
// No unwrapError – promise will resolve
});
// 3. Verify the resolved value contains the status
return promise.then((res) => {
expect(res.status).toBe(404);
expect(res.body).toEqual({error: 'Not found'});
});
To test the rejection path you can supply an unwrapError that throws when status is not 2xx:
m.request({
method: 'GET',
url: '/api/resource',
unwrapError: (res) => {
if (res.status < 200 || res.status >= 300) throw res;
},
});
In this case the promise will be rejected, and you can assert the catch handler in your component.
unwrapError hook. If it does, your mock should allow the hook to throw to trigger rejection.jest.fn()) to set status and responseText exactly as a real server would.readystatechange to 4 so that m.request settles.unwrapError is used, or on the caught error when it is.To fine‑tune the test, could you confirm whether the component you’re testing supplies an unwrapError hook? That determines whether the promise will reject or resolve in production, and therefore how the mock should be written.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.