5

I am writing tests for a REST client library which has to "login" against the service using the OAuth exchange. In order to prevent logging in for every endpoint I am going to test I'd like to write some sort of "test setup" but I am not sure how I am supposed to do this.

My test project structure:

  • test
    • endpoint-category1.spec.ts
    • endpoint-category2.spec.ts

If I had only one "endpoint category" I had something like this:

describe('Endpoint category 1', () => {
  let api: Client = null;

  before(() => {
    api = new Client(credentials);
  });

  it('should successfully login using the test credentials', async () => {
    await api.login();
  });

  it('should return xyz\'s profile', async () => {
    const r: Lookup = await api.lookup('xyz');
    expect(r).to.be.an('object');
  });
});

My Question:

Since the login() method is the first test there, it would work and the client instance is available for all the following tests as well. However, how can I do some sort of setup where I make the "logged in api instance" available to my other test files?

3 Answers 3

6

Common code should be moved to beforeEach:

  beforeEach(async () => {
    await api.login();
  });

At this point should successfully login using the test credentials doesn't make much sense because it doesn't assert anything.

Sign up to request clarification or add additional context in comments.

6 Comments

It throws an exception in case the login is not successful. The api.login method returns a promise which resolves to void. Same question applies here: I wanted to avoid logging in again for each test, because I would actually only need to login once to test all the other endpoints.
@kentor Yes, if beforeEach fails, this will fail all suite tests, this is expected behaviour. The thing you're describing should never be done in unit tests, each test is supposed be a fresh start, independent of other tests, because doing the opposite affects the state of a test and results in cross-contamination. Is OAuth in your test an expensive operation that does real XHR request?
Yes the oauth exchange consists of three requests in total for this specific case.
Real login() is called only in the spec that tests it, should successfully login using the test credentials (should be put into different describe to not share beforeEach with the rest of the specs). Other specs use mocked login, the way it's mocked depends on implementation, you can mock XHR requests or mock auth data directly in api instance (real login() is acceptable but undesirable in beforeEach because unit tests should be fast). That's how I would expect it to be done properly. Of course, new Client(credentials) should be done in beforeEach too to avoid cross-contamination
That's why I said that these are integration and not unit tests. It's a good thing to have integration tests but they can't substitute unit test coverage. I'd suggest to have both - they can be similar and redundant but they test different things. In unit tests you could mock the backend. before is executed once per describe block, so it suits your description. I cannot advice do to that because maintaining a common state between different tests is never a good practice, it's the same for unit, integration and e2e.
|
2
describe('Endpoint category 1', () => {
  let api: Client = null;

  beforeEach(() => {
    api = new Client(credentials);
  });

  afterEach(() => {
    // You should make every single test to be ran in a clean environment.
    // So do some jobs here, to clean all data created by previous tests.
  });

  it('should successfully login using the test credentials', async () => {
    const ret = await api.login();
    // Do some assert for `ret`.
  });

  context('the other tests', () => {
    beforeEach(() => api.login());
    it('should return xyz\'s profile', async () => {
      const r: Lookup = await api.lookup('xyz');
      expect(r).to.be.an('object');
    });
  });
});

4 Comments

The api.login() returns Promise<void>, however it could throw an exception, hence I did it this way. Do I still need asserts in that case? Also I assume beforeEach will be executed before all tests in that describe body. Your point is that the test environment should always be the same and hence I should also do a fresh login before each test?
The 1st question: yep, you need to assert the login errors in another use case(s), cuz it is totally different from the "login success" case.
And The 2nd one: a common principle in unit testing is that not bind multi tests together. It means that you can exec these tests all in one time or just exec one of them by each time. But how? That's the point. We stub, we mock, we create a bunch of data. And then what? We must reset all of these things to zero after each test is done, I mean, recover the system to the status before we exec each test.
Besides, Estus Flask is right, your code is totally not unit tests, but integration. In unit tests, we don't use the DB, networking or any other expensive things. We just stub or mock these things. Even more, we put a method in a little box, stub or mock every dependency it needs, control the input, and at last, we assert the output. That's unit tests.
0

Have you had a look at https://mochajs.org/#asynchronous-code ?

You can put in a done-parameter in your test functions and you will get a callback with this you have to call.

done() or done(error/exception)

This done would be also available in before and after.

When calling done() mocha knows your async-code has finished.

Ah. And if you want to test for login, you shouldn't provide this connection to other tests, because there is no guarantee of test order in default configuration.

Just test for login and logout afterwards.

If you need more tests with "login-session", describe a new one with befores.

1 Comment

Yes I know that, but I am using async / await in order to avoid such callbacks :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.