-
Notifications
You must be signed in to change notification settings - Fork 112
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
Can't test expected error responses because superagent throws #75
Comments
Hey @motiz88 thanks for the issue. It's a great point - but I think SuperTest is doing the right thing here, I also think Chai-HTTP is doing the right thing here. I think the problem might lie with Chai; any Promise which rejects will throw in async, and not every rejecting Promise is catastrophic enough for a test to fail. Do you fancy raising this issue upstream in chai? We can look at potential solutions there. |
I very respectfully disagree :) Promise rejection is to async control flow as throwing exceptions is to sync control flow. The same way any exception will by default fail a test (unless guarded against, wrapped in a So the issue at hand is that Chai-HTTP is misusing promise rejection to force its opinion as to correct handling of HTTP status codes, in a way that makes writing a certain kind of useful test case more difficult. Is there any value to baking this opinion into Chai-HTTP over letting test authors plug in their own domain-specific, test-specific logic? After all, that's what they'll be doing anyway. I mean, can a test author get away with not specifying the result of their request? I doubt you'll find a lot of tests like this: it('works', function() {
return chai.request(app).get('/');
}); But rather more like this: it('works', function() {
return chai.request(app).get('/')
.should.eventually.have.status(200); // With chai-as-promised
}); A more correct Promise-based API, for me, would be in line with the W3C Fetch API, where fetch(url)
.then(res => {
if (res.status >= 200 && res.status < 300) {
return res;
}
throw new Error(`${response.status} ${response.statusText}`);
})
.then(res => ....); |
More concisely, Chai-HTTP is basically doing my |
Sorry @motiz88 I probably summarised too much with me above comment, here's a more elaborate answer: Chai-HTTP simply exposes SuperTest's API but using Chai compatible assertions. SuperTest implements SuperAgent, with extra test utility. SuperAgent returns a callback with errors for What I'm saying is - you're not wrong; in that it is making assumptions about your API. But the problem is probably best solved elsewhere. Chai-HTTP couldn't fix the issue without silly hacks or very precise workarounds for the SuperAgent API. The way I see it you have 6 avenues:
Thoughts? |
My actual workaround is along the lines of: // with serverAddress lifted from chai-http
const appFetch = (path, ...args) => fetch(serverAddress(appServer, path), ...args);
// ...
const res = await appFetch('/faketable');
res.should.have.property('status', 404); Anything else (e.g. your suggestion in 6) quickly becomes non-semantic, more fragile and less readable. Imagine testing for a specific status code (my initial use case) - would you ever use My other replies are:
|
Could you explain in more detail what you hope to achieve here? Bear in mind that the only Promise logic chai-http currently has is If you plan to take function handleErrors(promise) {
return promise.catch((err) => {
if (err.code == 404) return err;
throw err;
});
}
describe('Querying a nonexistent table', function() {
it('causes a 404', async function() {
const res = await handleErrors(chai.request(app).get('/faketable'));
res.should.have.status(404);
});
}); |
Actually, I'd do if (err.response)
return err.response; in that handler. Which appears to be in line with SuperAgent's public API ("opinionated" response-based errors will have Yes, it could be done this way in userland. But the goal of writing clear test cases with minimal boilerplate takes a hit. My suggestion (and I'm willing to try and put together a PR to that effect) is to bake that logic into Chai-HTTP in a minimal, opt-in fashion. |
And, at least in this dev's experience, the current behavior violates the principle of least surprise. |
how would you do this though? That's the bit I'm not following. |
Well, assuming this functionality is desired, there are some questions of scope/consistency, then style and bikeshedding. Here are the main ones in my view.
|
Right now I like |
It's not really Promises that are the problem here, its the way I think I'd prefer to see the behaviour at a call site level, as globally it could be a bit of a footgun. Before we implement a change here though (and I'm not saying we definitely would right now), I'd like to at least raise the issue with SuperAgent to see if we can have an opt-in way of not erroring on bad status codes on their end. |
@keithamus, did you have any chance to discuss this with SuperAgent? Found this issue after struggling myself while trying to test response failures and I felt the default behavior was counter intuitive. I did fork and implemented your last suggestion to move forward w/ my tests and I am going to submit a PR for at least re-initiate this discussion. thanks! |
Hey @farmisen - sorry I have not had the time to raise this issue with SuperAgent. I've had a look at your PR and while it looks good at first glance, I think my comment still stands, we should raise this with SuperAgent before continuing. Would you like to raise it in the SuperAgent issue tracker? |
@keithamus this seems to be the responsibility of chai-http. It would be helpful if you accepted @farmisen 's PR :) |
I'm trying to pick up the thread of this discussion, where using chai-http has also violated my own principle of least surprise and I'm confused as to how I should proceed. I think the crux is that HTTP 4xx are client errors, whereas HTTP 5xx are server errors. When I'm testing a server with chai-http, I want to check that the server responds correctly to a misbehaving client. SuperAgent's goal is to be a "progressive client-side HTTP request library", so I think their design is reasonable: 4xx errors and 5xx errors are all equally bad. chai-http's goal does not entirely overlap with superagent. Client errors are "differently bad" in a test harness, and for this reason I think it's firmly chai-http's responsibility to differentiate between them in the API. So @keithamus, I also very respectfully disagree here, although I sympathise that it will make the superagent-wrapping implementation less elegant. :-) |
FYI it looks like ladjs/superagent#1069, when implemented, might be the solution we're after. |
If SuperTest uses SuperAgent's api in such a way that the final test callback does not receive an error on I can intuitively use SuperTest without knowing much (if anything) about SuperAgent. The case should be the same for chai-http. |
ladjs/superagent#1069 has been fixed and released as The change:We need to use Who can make this PR?Ideally, as this is a reasonably straightforward fix, and we would like to encourage new contributors to the Chai community, I would like to suggest new contributors and yet-to-be contributors only. If you have made a few PRs, or if you think you could complete this easily, then we have lots of other issues across our organisation which could be looked at instead. I want to make this PR!Great! If you're an existing contributor who is still new, or have yet to contribute to chai, then this is the PR for you. You should take a look at our Code of Conduct and Contribution Guidelines (note: we need to work on our contribution guidelines, so these might not apply exactly to this sub-project). If you want to work on this, you should comment below, something like:
If you don't comment you might run the risk of someone else doing the work before you! If someone has already commented, then you should leave this PR for them to work on. If someone has commented, but a few weeks have passed - it is worth asking the person who has commented if they're still interested in working on this, and if you can pick it up instead. How to make a PR for this change:Firstly, you'll need to update SuperAgent to function Test() {
Request.call(this, method, path);
this.app = app;
this.url = typeof app === 'string' ? app + path : serverAddress(app, path);
this.ok(function () { return true });
} You'll also need to add some tests (perhaps, around L76 of test/request.js that reflect the changes. Perhaps something like: it('succeeds when response has bad status', function (done) {
request('https://httpbin.org/status/400')
.end(function (err, res) {
res.should.have.status(400);
done(err);
});
});
it('resolves as a promise when given bad status code', function () {
return request('https://httpbin.org/status/400')
.then(function (res) {
res.should.have.status(400);
});
}); What happens then?We'll need 2 contributors to accept the change, and it'll be merged. Once merged, we'll need to take some time to make a manual release, which will take a while - as we need to prepare release notes. It'll likely be released as chai-http@4.0.0 because this will be considered a breaking change. |
Hi, would it be okay if did this one? |
sorry @oluoluoxenfree, I didn't see your comment. |
@oluoluoxenfree sorry you missed out there. With our next issue which can be resolved with a PR, you will get first refusal to solve it. I'm also going to work on some guidelines to ratify all of this. @richardpringle thanks very much for the awesome work - next time before working on the PR if you would so kindly announce in the issue comments, that'd be swell. 👍 😄 |
Any idea when this will be released to npm? |
@wdullaer You could install it by this commit without waiting for a npm release: npm i https://github.com/chaijs/chai-http#3ea4524 --save-dev |
A solution that works right now without a PR is to catch the error with it('should handle invalid ids', () => request(app)
.get('/v1/foo/bogus/')
.then(
() => expect.fail(null, null, 'Should not succeed.'),
({ response }) => {
expect(response).to.have.status(404);
expect(response).to.be.json;
expect(response.body.success).to.equal(false);
expect(response.body.errors[0]).to.include('not found');
}
)
); Edit: Updated example based on @Yitzi2's feedback. |
I'm using async/await try {
await chai.request(app).get('bad/path').send()
expect(true).to.be.false // Fail the test
} catch (err) {
expect(err).to.have.property('status')
expect(err.status).to.be.equal(400) Now if the request returns 200 (which should make the test fail), it drops to the |
shannonmoeller, that's a good solution, though you probably want a then block too, so that if it does (incorrectly) return a "valid" status code, the test will fail rather than not running. |
@Yitzi2 Good call. I'll edit the example to include that. |
@oreporan You need to assert on the try {
await request(app).get('/v1/foo/bogus/');
expect.fail(null, null, 'Should not succeed.');
}
catch ({ response }) {
expect(response).to.have.status(404);
expect(response).to.be.json;
expect(response.body.success).to.equal(false);
expect(response.body.errors[0]).to.include('not found');
} |
Guys, just follow @inancgumus comment #75 (comment) since when this will got published, you will have to change your code. EOT |
@keithamus Given this is a breaking change, can we assume that chai-http will be getting a major-version bump when this is released? |
@shannonmoeller |
This is the approach we use for testing 404's and other errors like 401, 403, 409 and 422. Checking the spy should ensure that if the path responds with 20x status, the test should fail. 'use strict';
const chai = require('chai');
const chaiHttp = require('chai-http');
const spies = require('chai-spies');
chai.should();
const { app } = require('../server');
chai.use(chaiHttp);
chai.use(spies);
describe('Wrong paths', function () {
it('should respond with 404', function () {
const spy = chai.spy();
return chai.request(app)
.get('/v1/foobar')
.then(spy)
.catch((err) => {
const res = err.response;
res.should.have.status(404);
})
.then(() => {
spy.should.not.have.been.called();
});
});
}); |
Any update on handling errors properly? |
@elias-garcia I believe that this will be fixed when #184 gets merged and we finally release a new version of To workaround this you can catch the catching chai
.request(app)
.get('/')
.catch(err => err.response)
.then(res => {
expect(res).to.have.status(404)
}) installing npm i --save-dev https://github.com:chaijs/chai-http chai
.request(app)
.get('/')
.then(res => {
expect(res).to.have.status(404)
}) |
February 2018, still not published on NPM? I'm still getting this same problem. 2 year issue.. |
Any plans for release? We are waiting for proper testing... |
@gigi, I'm pretty sure this has been released in 4.0.0 @keithamus, I'm pretty sure this issue can be closed |
Thanks @richardpringle! |
Hi, |
* chai-http currently does not support to properly use 'expect' to specify the bevahiour of calls returning 4XX or 5XX status codes * chaijs/chai-http#75 (comment) * ladjs/superagent#1069 (comment)
i am new to node js and chai. and response i am getting is Not Found\nThe requested UR\nL was not found on the server. If you entered the URL manually please check you\nr spelling and try again. \n"} can anyone help me out where i am doing wrong? |
all: sorry for the spam you got in your inboxes for being subscribed to this closed topic. @vidya3105 this is not the right place nor time for that question. Does not fit the topic (ES7 async/await), and is doubly bad because the issue has been closed. There are a dozen people that got your message in their inboxes when they really shouldn't have. Checking the response you got, you can see that the http query worked -- you got a reply from the server. The problem is either you set the wrong URL, or you have a problem with your web server. Next time, open an issue if you find a bug or have a feature request. If looking for general help I'd suggest stackoverflow. In there you should also provide a minimal, verifiable and complete example. It's impossible to help you further since we can't reproduce it. Sorry for the bluntness :P -- please don't take it the wrong way! Welcome! :-P but wrong place for that kind of questions! <3 |
Hi guys, the .catch(err => err) help me to return the error and save it to the error variable inside the try block.
|
Consider the following Mocha test (written in ES7
async/await
style which is syntactic sugar for Promises):If the endpoint returns a 404, the response promise gets rejected with an
Error
originating in superagent's response handler, and the test fails before reaching the status assertion. Other 4xx/5xx status codes similarly cannot be tested for in this way. I think this is surprising behavior that makes testing error responses much harder than it should be.The text was updated successfully, but these errors were encountered: