Skip to content
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

Testing with api routes? #130

Closed
tettoffensive opened this issue Jun 4, 2020 · 5 comments
Closed

Testing with api routes? #130

tettoffensive opened this issue Jun 4, 2020 · 5 comments

Comments

@tettoffensive
Copy link

I'm testing some Next.js API routes using the following.

describe('/api/logout handler', () => {
  let server: http.Server;
  let url: RequestInfo;

  beforeAll(async (done) => {
    server = http.createServer((req, res) => apiResolver(req, res, undefined, handler, undefined));
    url = await listen(server);
    done();
  });

  afterAll((done) => {
    server.close(done);
  });

My API route handler is wrapped in a withIronSession. And I would like to do something like:

withIronSession(async (req) => {
      expect(req.session.get('user')).toMatchObject(expectedValue);
}, sessionOptions);
  1. I can't seem to get the expectation inside the handler to fire
  2. I don't think the session values are persisting in a testing environment

My ultimate goal is to have my tests confirm that my login and logout are properly storing/destroying credentials. Maybe the best thing to do is mock next-iron-session (though I had problems with that too: TypeError: (0 , _nextIronSession.withIronSession) is not a function)

What would you recommend?

@vvo
Copy link
Owner

vvo commented Jun 9, 2020

Hey there @tettoffensive:

  1. I can't seem to get the expectation inside the handler to fire

Could you create a CodeSandbox/reproduction repository to test that?
You can also have a look at how we test our own library here: https://github.com/vvo/next-iron-session/blob/9ef64bf55c85e5d9bafcdba334b9699ac940075c/lib/index.test.js which includes test where the session cookie is pre-filled

2. I don't think the session values are persisting in a testing environment

That I don't know either, it would depend what you're expecting but when testing usually you're not using a real browser, it means that the cookies even if set on the http response will not be stored in the requester (your testing environment/http library). This would explain your issue.

My 2 cents: it seems you're trying to unit test your wrapped API routes. Which means you'll also be unit testing/mocking withIronSession. But withIronSession is already tested so this could lead to weird testing code (hard to test) or duplicate testing. I would do this instead:

  • either use integration testing and test real API routes (with a browser/automation)
  • test your API routes with pre-computed session cookies just like we do in our own testing

Maybe there's something missing in the library withIronSession to ease what you're trying to do, not sure though about what is missing.

@tettoffensive
Copy link
Author

@vvo I ended up creating a mock and in my setup using applySession. Then I can use session.set() and session.get() in my tests and everything checks out.

class MockIronStore {
  private static instance: MockIronStore;

  private saved: { [key: string]: string | object | number };

  private unsaved: { [key: string]: string | object | number };

  private constructor() {
    this.saved = {};
    this.unsaved = {};
  }

  static getOrCreateStore(): MockIronStore {
    if (!MockIronStore.instance) {
      MockIronStore.instance = new MockIronStore();
    }
    return MockIronStore.instance;
  }

  get(key: string) {
    return this.unsaved[key] || undefined;
  }

  set(key: string, val: string | object | number) {
    this.unsaved[key] = val;
  }

  unset(key: string) {
    delete this.unsaved[key];
  }

  seal() {
    this.saved = { ...this.unsaved };
  }

  clear() {
    this.unsaved = {};
  }
}
function throwOnNoPassword() {
  throw new Error('next-iron-session: Missing parameter `password`');
}

function throwOnNoCookieName() {
  throw new Error('next-iron-session: Missing parameter `cookieName`');
}

const applySession = jest.fn().mockImplementation((req) => {
  const store = MockIronStore.getOrCreateStore();

  const session = {
    set: store.set.bind(store),
    get: store.get.bind(store),
    unset: store.unset,
    save() {
      store.seal();
    },
    destroy() {
      store.clear();
    },
  };

  req.session = session;
});

export default { MockIronStore };

module.exports = {
  throwOnNoCookieName: jest.fn().mockImplementation(),
  throwOnNoPassword: jest.fn().mockImplementation(),
  applySession,
  withIronSession: jest
    .fn()
    .mockImplementation(
      (
        withIronSessionWrapperHandler,
        {
          ttl = 15 * 24 * 3600,
          cookieName = throwOnNoCookieName(),
          password = throwOnNoPassword(),
          cookieOptions = {},
        },
      ) => {
        return jest.fn().mockImplementation((...args) => {
          const handlerType = args[0] && args[1] ? 'api' : 'ssr';
          const req = handlerType === 'api' ? args[0] : args[0].req;
          const res = handlerType === 'api' ? args[1] : args[0].res;

          applySession(req, res, { ttl, cookieName, password, cookieOptions });

          return withIronSessionWrapperHandler(...args);
        });
      },
    ),
};

And in my test setup:

const res: { session?: Session } = {};
  let session: Session;
  await applySession(res, undefined, { cookieName: 'test', password: 'test' });
  if (res.session) {
    session = res.session;
  } else {
    throw new Error('No session after apply session');
  }

@vvo
Copy link
Owner

vvo commented Jun 9, 2020

Thanks, closing this then!

@vvo vvo closed this as completed Jun 9, 2020
@pkabore

This comment has been minimized.

@vvo

This comment has been minimized.

@vvo vvo mentioned this issue Dec 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants