ladjs / supertest

🕷 Super-agent driven library for testing node.js HTTP servers using a fluent API. Maintained for @forwardemail, @ladjs, @spamscanner, @breejs, @cabinjs, and @lassjs.
MIT License
13.75k stars 758 forks source link

Supertest throwing 404, Client says 200 #255

Closed wesleygauntt closed 5 years ago

wesleygauntt commented 9 years ago

I have a route to get all friends in a database as follows:

router.get('/friends', function(req, res, next){
  Friend.find(function(err, friends){
    if(err){ console.log(err); }
    req.data = friends;
    next();
  })
});

Hitting this route with postman returns a list of all friends in the database successfully, and other queries from a web client behave normally. However, with Supertest when I try to test this route, a 404 is returned.

Here is the test:

var app = require('../server'); 
var should = require('should');
var supertest = require('supertest');
var request = supertest(app);

describe('Friends', function(){

  it('Should get all friends.', function(done){
    request
      .get('/friends')
      .expect(200)
      .end(function (err, res){
        res.status.should.equal(200);
        done();
      })
  });

});

Now, about 20% of the time, this test will execute successfully for no given reason. My question: Why does supertest return a 404, when all other methods to access the route return 200?

pourquoi42 commented 8 years ago

+1 I have a similar issue. In my case, I use a rest call in several tests. Sometimes it works, sometimes it gets a 404. I assume, that somehow supertest doesnt realise that the app is not ready yet.

Stunning that this issue is open for over a year. I guess I will drop supertest for superagent.

dan-kez commented 7 years ago

I'm also having the same issue

alvarolorentedev commented 7 years ago

+1 it does not work with my error handling middleware

twelve17 commented 7 years ago

Long shot here, but in my case, the error was caused by koa-cors. That middleware causes Koa to return a 404 if the origins do not match. (I switched to the official kcors gem and all is well now.) Anywho, it might be worth disabling middleware and testing again to see if you can rule out middleware behavior causing a 404 in your test environment.

b6t commented 6 years ago

+1 anyone find a solution to this? I have a restify api that with the same behavior, test = 404, but postman = 200. It looks like the test a are firing before the app is ready, but in restify, as far as i can tell, you cant emit an event for mochas before function.

Arinzeokeke commented 6 years ago

Hi, I'm having the same issue! Some of my routes throw 404 in my tests but when I literally copy/paste it to postman, it works

b6t commented 6 years ago

@Arinzeokeke im not really sure what the true solution is and off the top of my head dont remember how I specifically fixed this. However, I do use jest now and in jest I use supertest to make the api request. Again I know this is not 'The Solution' but a work around.

const appRoute = require('app-root-path')
const api = require(`${ appRoute }/app`)
const request = require('supertest')

const badReq = { }

it('Should fail validation', () => {
  return request(api)
    .post('/my/route')
    .send(badReq)
    .then(res => {
      expect(res.status).toEqual(400)
    })
})

and thats run by npm test which runs jest.

PeterIttner commented 6 years ago

I am still having the same issue. Is there an update on that issue so far?

DublinDev commented 5 years ago

I seem to be having this same issue (postman returning 200, supertest returning 404 for patch requests only). I can see this was closed but was this functionality ever resolved?

arodriguezju commented 4 years ago

In my case I am using routing-controllers package, and for some reason the decorators seem to be an issue.

A request to this endpoint returns 200 in Postman, but 404 in supertest:

@Post()
@OnUndefined(200)
public reboard(@CurrentUser() user: User, @Body() body: Reboarding, @Res() res: Response, @Session() session: any) {     
        return undefined;
    }

This one returns 200 in both agents:

@Post()
public reboard(@CurrentUser() user: User, @Body() body: Reboarding, @Res() res: Response, @Session() session: any) {     
        return res.sendStatus(200);
    }

The only difference between both requests is that the first one does not return an 'OK' in the response's body. The second one sends the 'OK' body along.

It can be that supertest does not handle an empty response correctly, but I don't think that is the case.

It is most probably that routing-controllers @OnUndefined decorator is not working properly during tests for some reason. According to the documentation:

If your controller returns void or Promise or undefined it will throw you 404 error. To prevent this if you need to specify what status code you want to return using @OnUndefined decorator.

So it is probable that the decorator is being ignored.

I hope it helps!

lissau commented 3 years ago

For anyone stumbling across this from Google:

Make sure you are using .post() to fetch a post endpoint and .get() for a get endpoint.

I know. Pretty silly, but that was my mistake and why I was getting 400's when postman reported 200

younes-io commented 3 years ago

Late to the game, but make sure your app.js file exposes the routes.

niteshrawat1995 commented 3 years ago

I face the same issue!

niteshrawat1995 commented 3 years ago

@younes-io I have an API route (prefix with "api") that is present in the app.js file and all other routes are used inside this API route. All these routes return 404 when used with supertest.

App.js app.use(apiRoutes);

Routes.js

const apiRouter = new Router({ prefix: '/api'});
apiRouter.use('/club', ClubRoutes); // these are separate router files
apiRouter.use('/event', EventRoutes); // these are separate router files
export default apiRouter.routes();

now when testing route GET api/club/, I get 404 but get 200 in postman

alberthaven commented 2 years ago

is there any update in fixing this issue?

ethan-davis-seekout commented 2 years ago

I have the same problem without a solution. Why was the issue closed?

EDIT: My problem was awaiting database initialization for testing. To fix my problem, if my NODE_ENV is testing, then I don't await database initialization. I can mock database query results.

tenesh commented 2 years ago

I had similar issue too using supertest. The highlight here is to make sure you are using .post() and .get() correctly. For instance,

This is the app.ts file where I initiate the routes.

const app = express();
app.set('trust proxy', true);
app.use(json());
app.use(
    cookieSession({
        signed: false,
        secure: true
    })
);

app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);

app.all('*', async (req, res) => {
    throw new NotFoundError();
});

app.use(errorHandler);

export {app};

This file signup.ts for user signing up.

router.post('/api/users/signup', (req, res) => {
});

In your test file, you have to make sure if testing this signup.ts file, you have to use .post() rather than .get() because that is the route initialized in the signup.ts. Something like this:

it('returns 201 on successful signup', async()=>{
   return request(app)
       .post('/api/users/signup')
       .send({
           email: 'test@test.com',
           password: 'password'
       }).expect(201)
});

In case, where it works in Postman because it assorts to the request type automatically.

Hope this helps.

sakrlog commented 2 years ago

WAW, still facing the same issue. It's been 5 years since this issue was created

eugene0928 commented 2 years ago

Hi, I have also such error

moscoso commented 1 year ago

So i ran into this problem as well. And I found a problem I was able to fix.

This was happening in tests that were hitting an endpoint with route params. We were generating random strings for our route params with Chance library. It appears as if there is an arbitrary length limit to how long a string can be in supertests routes.

Once we changed it to use a shorter string using guid library the tests passed every time.

AyoubTahir commented 1 year ago

Same here

gabrielcipriano commented 1 year ago

Hey @AyoubTahir could you provide more details?

devpolo commented 1 year ago

For NestJS users, it looks like starting the application with app.listen(0) solved it:

describe('Cats', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    })
      .compile();

    app = moduleRef.createNestApplication();

    await app.init();
    await app.listen(0) // Add this line to start the application
  });

  afterAll(async () => {
    await app.close();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer())
      .get('/cats')
      .expect(200)
  });
});
brendantruleo commented 8 months ago

@rimiti Experiencing this issue 8 years later, random API routes fail about 20% of the time. Is there a workaround to this at all?

andregit1 commented 4 months ago

2024, using supertest for login endpoint testing still return 401, never touch 200

stroiman commented 3 months ago

Why you get 404, I don't know - the server is not even receiving the request in some cases, so really there shouldn't have been a response. But there is a problem in the constructor for the Test class - which I don't know how to fix :(

Problem is that listen() is not synchronous, but it is called from the constructor, which is. listen takes a callback that will be called when the server is listening, or you can wait on the listening event.

There is a comment in the end function about waiting for the server to be listening, but as far as I can tell, you can't fix it there, because it calls the overridden end function that assumes that the server is listening.

I think I will just let my test start the server directly, not constructing supertest until the server is actually listening.

  constructor (app, method, path) {
    super(method.toUpperCase(), path);

    this.redirects(0);
    this.buffer();
    this.app = app;
    this._asserts = [];
    this.url = typeof app === 'string'
      ? app + path
      : this.serverAddress(app, path);
  }

  /**
   * Returns a URL, extracted from a server.
   *
   * @param {Server} app
   * @param {String} path
   * @returns {String} URL address
   * @api private
   */
  serverAddress(app, path) {
    const addr = app.address();

    if (!addr) this._server = app.listen(0); // <-- HERE - not waiting for server to be listening. This NEEDS to be async, but called from a constructor which cannot be async.
    const port = app.address().port;
    const protocol = app instanceof Server ? 'https' : 'http';
    return protocol + '://127.0.0.1:' + port + path;
  }
stroiman commented 3 months ago

Argh, after all, it didn't seem to be related to waiting for the server to listen, at least that wasn't the entire problem.

I changed the code, so my test is explicitly starting a server, and if I don't specify a port, i.e. listen(0), it will randomly fail with a 404. Running with debugging shows that express (which I am using here), is not being called at all. And when it fails, virtually all tests fails, since I only start the server once for the entire test suite.

However, if I explicitly set a port, it seems to work just fine.