Skip to content

[COL-56] Add integration tests #229

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

Merged
merged 2 commits into from
Oct 30, 2023

Conversation

lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Oct 20, 2023

This adds integration tests that test the core functionality of the SDK. It's currently a draft so I can get feedback on the approach and some of the questions that came up whilst I was working in this (see TODO comments in code).

On top of what's currently here, I intend to also add a test for component locking. (I've added a component locking test, too.)

@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 14:26 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch 2 times, most recently from dbaaeac to e19196e Compare October 20, 2023 14:30
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 14:31 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from e19196e to 0a42b8b Compare October 20, 2023 14:32
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 14:33 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 0a42b8b to 0456e4e Compare October 20, 2023 19:32
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 19:33 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 0456e4e to a20bd55 Compare October 20, 2023 19:35
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 19:36 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from a20bd55 to 987fc91 Compare October 20, 2023 19:36
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 20, 2023 19:37 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 987fc91 to 023db43 Compare October 23, 2023 13:35
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 23, 2023 13:36 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 023db43 to f126758 Compare October 23, 2023 13:39
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 23, 2023 13:40 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from f126758 to dd85414 Compare October 23, 2023 13:41
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 23, 2023 13:42 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from dd85414 to 2f9332a Compare October 23, 2023 13:44
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 23, 2023 13:45 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 2f9332a to fba94db Compare October 23, 2023 13:46
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 23, 2023 13:46 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from fba94db to 1dd4ab5 Compare October 25, 2023 12:34
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 25, 2023 12:35 Inactive
@lawrence-forooghian lawrence-forooghian changed the base branch from main to upgrade-ably-to-1.2.46 October 25, 2023 12:38
@dpiatek dpiatek force-pushed the upgrade-ably-to-1.2.46 branch from e005146 to 997f4c0 Compare October 26, 2023 10:15
Base automatically changed from upgrade-ably-to-1.2.46 to main October 26, 2023 11:45
I’ll reuse it in the upcoming integration tests.
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 1dd4ab5 to 54cc3dd Compare October 26, 2023 11:46
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 26, 2023 11:47 Inactive
() => {
it('space members', async (context) => {
// Given Spaces clients `performerSpaces` and `observerSpaces`, both configured to use the same API key, and each configured to use a different randomly-generated client ID...
const [{ spaces: performerSpaces, clientId: performerClientId }, { spaces: observerSpaces }] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is good in this file, but I think some of the comments really get in the way of following efficiently what's happening in the file. I'd review every comment and focus on two things:

  • is the intent of a given line of code clear or could be part of the summary?
  • is an API problematic to test, we have to work around its limitations and explain why?

There are instances of both for sure in this file, but many comments, like the one above, feel very unnecessary or could be part of the utility function, instead of being repeated multiple times per test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've slimmed down the comments and will push the updated version shortly.

const performerSpace = await performerSpaces.get(spaceName);
const observerSpace = await observerSpaces.get(spaceName, { offlineTimeout: 5000 });

// (scenario_1_1: entering a space)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think we should try to take advantage of nested before and describe blocks instead of using comments for scenarios. It makes it easier to update and understand a test section-by-section instead of having to parse the whole test to debug it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I'm not sure exactly what it is that you're suggesting here. What it sounds like is that, given a single test that’s written linearly like this:

scenario_1:
  doSomething_1
  checkSomething_1

scenario_2:
  doSomething_2
  checkSomething_2

scenario_3:
  doSomething_3
  checkSomething_3

then you're suggesting that we instead write it as nested test cases something like this?

describe('scenario_1', () => {
  beforeEach(() => {
    // do the actions of doSomething_1 and somehow expose a way for the `it` block to check its results
  });

  it('some description of scenario_1', () => {
    // do checkSomething_1
  });

  describe('scenario_2', () => {
    beforeEach(() => {
      // do the actions of doSomething_2 and somehow expose a way for the below `it` block to check its results
    });

    it('some description of scenario_2', () => {
      // do checkSomething_2
    });

    describe('scenario_3', () => {
      beforeEach(() => {
        // do the actions of doSomething_3 and somehow expose a way for the below `it` block to check its results
      });

      it('some description of scenario_3', () => {
        // do checkSomething_3
      });
    });
  });
});

Have I got that right? If so, a couple of things that I'd wonder about:

  • isn't this code going to get very indented, very quickly?
  • I'm not sure how it achieves "instead of having to parse the whole test to debug it" — given that each scenario relies on the side effects given in the preceding scenarios, don't you still have to understand all of the preceding sections in order to confidently edit a given section?

If I've misunderstood what you're suggesting, would you mind perhaps showing a concrete example of what you had in mind?

Copy link
Contributor

@dpiatek dpiatek Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this code going to get very indented, very quickly?

this is how the unit tests are structured as well, I'm not sure why that's an issue? (I might be biased - for me this is pretty standard in JavaScript and Ruby ecosystems).

I'm not sure how it achieves "instead of having to parse the whole test to debug it" — given that each scenario relies on the side effects given in the preceding scenarios, don't you still have to understand all of the preceding sections in order to confidently edit a given section?

with your current structure, if a test fails it's something like:

cursors test failed - failed
// expectation which failed

So we need to go over all the lines of code up to the point that a given expectation has failed. Compare that to:

cursors test
  assertion description and setup 1 - passed
  assertion description and setup 2 - passed
  assertion description and setup 3 - failed

Without looking at code or expectations, we can see what assertion, assumption fails, and also which ones are still good. With a linear setup, it's likely no assertion passes from the first failure, but that's fine. This is how Cucumber specs work in Ruby. https://github.com/ably/website/blob/main/features/authentication_a.feature#L11-L24

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just adding a comment to record that we had a chat about this and it turns out I was a bit thrown by your usage of the term "nested", where you actually just meant to keep the structure of the tests as-is but to nest each test case in a describe, put the setup part into a beforeAll and each scenario into its own it. I've made those changes and will push them shortly.

// Note: I think that there are situations in which this listener might not get called, for the same reasons as described in commit 6e77941 — that is, the call to `performerSpace.enter()` might result in `observerSpace` seeing a presence UPDATE instead of an ENTER and hence not emitting a members `enter` event. I haven’t observed this happening in practice yet, and I’d like to be able to test that the SDK emits a members `enter` event, so I’m going to keep the test as it is for now. If we can think of a way for this test to be sure that a members `enter` event will be emitted, then we should implement it.
membersEnter: new Promise<MembersEventMap['enter']>((resolve) => {
observerSpace.members.once('enter', resolve);
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the listener for all events (i.e. not pass an event to members.subscribe) or use getAll to check the membership? These APIs will be much more commonly used than once in real-world apps.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I change this, I want to be sure I understand what you're suggesting. Are you saying that you don't want us to use once at all in this test file? And, if so, would using subscribe and then removing the subscriber when the listener is called — which would emulate the behaviour of once and keep the test structure as it is — be OK, or are you wanting us to only call subscribe once at the start of the test and then look at which events the listener receives as we perform various actions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and are you talking only about the members test, or all of them?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this was just referring to members, and in particular the comment above and your worry about present events. once forces you to pass an event name, while you can use subscribe to capture everything, including present events (which honestly, as I write it, feels like a bug because it's not obvious at all).

And I proposed getAll because it waits for the presence set to sync, I thought would be useful in tests too (as at least in theory, you can use it to expect some state to sync - same as once). But I don't mind using once for tests - it's still a valid approach.

To be clear, I'm not requesting any changes here, I was responding more to the comment and reference to enter/present behaviour.

// ...and that the following is true for each of the space `update` event’s member and the members `enter` event:
for (const member of [eventsTriggeredByEntering.spaceUpdate.members[0], eventsTriggeredByEntering.membersEnter]) {
// ...its `clientId` matches that of `performerSpaces`...
context.expect(member.clientId).toEqual(performerClientId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why use context.expect here and not expect if we importing descirbe and it ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the guidance for running tests in parallel:

When running concurrent tests, Snapshots and Assertions must use expect from the local Test Context to ensure the right test is detected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha 👍🏻

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, now that we have nested describe blocks the tests are no longer working in parallel (will add a comment to commit message explaining what I tried), so I've switched everything back to using top-level expect anyway.

context.expect(member.clientId).toEqual(performerClientId);
// ...and its `profileData` matches that passed to `performerSpace.updateProfileData()`...
context.expect(member.profileData).toEqual({ name: 'Luna Gomes' });
// ...and its `isConnected` continues to be true.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an example of comments that I don't think are necessary (because the code reads almost as the English equivalent), I think all the comments from lines 86 up to here are not needed.

// 2. one of its `get*()` methods is called
// 3. its `subscribe` or `unsubscribe` method is called
//
// This seems to mean that a client that sends cursor updates but does not listen for them will drop the first update passed to `cursors.set()`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected behaviour and your workaround is fine; if this isn't clear from docs we should update them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's clear from the docs. Is there any action needed on this PR related to this?

Copy link
Collaborator Author

@lawrence-forooghian lawrence-forooghian Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'll remove the TODO part of the comment)

const observedCursorEventsData: CursorsEventMap['update'][] = [];
// TODO Decide what to do about the below comment. Should we expose the completion of the attach operation via the cursors.subscribe() API, the same way as ably-js exposes it through `presence.subscribe()`?
//
// Note that the `cursors.subscribe()` API does not currently provide any way for the user to know that the channel attach operation triggered by calling `presence.subscribe()` has completed. So, we have no guarantee that the `observerSpace.cursors.subscribe()` listener will receive the cursor positions sent by the calls to `performerSpace.cursors.set()` below. (However, the 1 second wait below makes it likely.)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the .channel property on cursors not be used for that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also potentially expose a method for users to have more control of channel attachment, that's synced with what subscribe/unsubscribe does.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was originally hesitant about directly interacting with the underlying Ably channel in the tests, but I've changed my mind and will do so for this and for this one too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also potentially expose a method for users to have more control of channel attachment, that's synced with what subscribe/unsubscribe does.

Yep, I've updated my comment to mention this as an option.

// (End of setup for assertions_2_2)

// ...and then wait 1 second (The idea being that this is hopefully long enough for `performerSpace.cursors` to find out about the presence enter operation triggered by calling `observerSpace.cursors.subscribe()`. I also considered instead adding a presence subscriber to `performerSpaces`’ Realtime client but wasn’t sure whether we should be messing with this client in what’s meant to be a test that somewhat simulates real-life user behaviour. If we find out that 1s is not enough, however, then I’d suggest perhaps implementing this alternative solution instead of increasing the timeout)...
await setTimeout(1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if this timeout is removed? does the test become flaky?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, performerSpace will drop the cursor updates and cursorUpdatesPromise will never resolve. I've added the following text to the comment to clarify this:

and hence not drop the cursor updates that we pass to set()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

understood 👍🏻

Copy link
Collaborator Author

@lawrence-forooghian lawrence-forooghian Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As alluded to here I'm going to change this to make it more deterministic by observing performerSpace.cursors.channel.presence and removing the timeout.

},
};

// TODO Why is space1 emitting anything here? The emitted events make it appear as though space1 has lost the lock but space2 also failed to acquire it, which seems surprising.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a side-effect of how messages are processed right now; it is not desired but expected right now (there are tickets in the backlog to make that cleaner).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue that I could add a link to here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this particular case, no - if you could open one, that would be great.

For now, I'd leave a comment here that this behaviour is expected, but not desired.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created https://ably.atlassian.net/browse/COL-549 and will add a comment about it.

@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 54cc3dd to 3e6ef85 Compare October 26, 2023 20:03
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 26, 2023 20:03 Inactive
@lawrence-forooghian
Copy link
Collaborator Author

@dpiatek I've also added tests for locks.get() before and after acquiring a lock, will push shortly.

@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 3e6ef85 to 48ba6a9 Compare October 27, 2023 17:47
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 27, 2023 17:48 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 48ba6a9 to 2dc61a1 Compare October 27, 2023 17:49
@github-actions github-actions bot temporarily deployed to staging/pull/229/typedoc October 27, 2023 17:49 Inactive
@lawrence-forooghian lawrence-forooghian marked this pull request as ready for review October 27, 2023 17:50
let observerSpaces;
let performerClientId;
let performerSpace;
let observerSpace;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One final remark @lawrence-forooghian - do you think it's worth outlining the approach taken in this test at the top of the file, similar to what you have done in the commit? I think it might not be obvious that each it is treated as a step in the spec instead of an individual test, and they share state in a describe block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, have done.

This adds integration tests that test the core functionality of the SDK.

I had originally written these as a single `describe` containing four
`it`s, each of which included the test setup and then a list of
scenarios, these scenarios just being separated by comments. However,
Dom was keen for me to make more use of the vitest APIs for structuring
my tests. This resulted in the current setup, where there is one
`describe` for each area of functionality, each of these `describe`s
then containing multiple `it`s. Each `it` within a given `describe`
relies on the side effects of the previous `it`s within that `describe`.

Dom preferred this new approach and was happy to accept the compromises
that this resulted in, such as having `describe`-level local variables
which have no type information, and the fact that the failure of one
`it` does not prevent the subsequent `it`s (which rely on the
preceding `it`s all having succeeded) from running.

With my initial approach, I was able to get the tests running in
parallel using `describe.concurrent` on the top-level `describe`. I
wasn’t able to figure out how to achieve this in the new approach, in
which we want to run all `describe`s in parallel, but run the `it`s
within a given `describe` sequentially. I tried using `.concurrent` on
the top-level `describe` and `.sequential` on each nested `describe`,
but it still just ran everything sequentially.

Resolves COL-56.
@lawrence-forooghian lawrence-forooghian force-pushed the COL-56-add-integration-tests branch from 2dc61a1 to aafe68b Compare October 30, 2023 13:06
@lawrence-forooghian lawrence-forooghian merged commit a2b0d08 into main Oct 30, 2023
@lawrence-forooghian lawrence-forooghian deleted the COL-56-add-integration-tests branch October 30, 2023 13:07
@lawrence-forooghian lawrence-forooghian changed the title [COL-56] Add integration tests (WIP) [COL-56] Add integration tests Oct 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants