IBM / slack-wrench

Tools to build and test Slack apps
Apache License 2.0
48 stars 26 forks source link

Support newer Slack and Jest versions #123

Closed Mr0grog closed 2 years ago

Mr0grog commented 2 years ago

Related Issue
Supports #118, #104

Related PRs
This PR is not dependent on any other PR

What does this PR do?
This is an attempt, based on the discussion in #118, to make jest-mock-web-client compatible with newer Slack and Jest versions, and in general with a wider array of versions by making it a bit more abstract — it builds its typings dynamically by mapping those of the installed Slack version. While I was at it, I wound up solving #104.

Description of Changes
This implementation is a bit complicated, partially because I tried to preserve MockWebClient as a class, rather than making it just a type. (If just a type is OK, this can definitely be simplified a bit!)

The main idea here is to construct the types for MockWebClient by mapping the types for Slack’s WebClient class. This is mainly done with the ObjectWithMocks<Type> type. Then the deepCopyWithMocks<T> function is used to implement that ObjectWithMocks<Type> by making a copy of an object and recursively replacing any functions with Jest mock functions. Finally, the MockWebClientConstructor function is used to make a class MockWebClient that inherits concrete versions of those new types (as applied to WebClient), and that acts just like MockWebClient does today. This part is definitely a little unusual, and hopefully the comments in the code do a good job of explaining what’s happening. The function, along with its extra type coercion, are needed because you can’t map types in a class definition, and a class can’t inherit from a type.

Alongside all that, I:


If MockWebClient doesn’t need to be a class, this can definitely be a little simpler to read and understand. See this commit: https://github.com/Mr0grog/slack-wrench/commit/48a09b8a82405e19d305660220797b22b7d442ff

Basically, all the stuff from the declaration of MockWebClientConstructor down through MockedWebClient could be replaced with:

export type MockWebClient = ObjectWithMocks<Slack.WebClient>;

type MockConstructor = new (
  token?: string,
  options?: Slack.WebClientOptions,
) => MockWebClient;

export const MockedWebClient: jest.MockedClass<MockConstructor> = jest.fn(
  function makeMockWebClient(this: MockWebClient) {
    const exampleClientInstance = new WebClient('MOCK_TOKEN');
    const instance = deepCopyWithMocks<Slack.WebClient>(exampleClientInstance);

    // Default for bolt apps
    // https://github.com/slackapi/bolt-js/blob/1655999346077e9521722a667414758da856ede2/src/App.ts#L579
    instance.auth.test.mockResolvedValue({
      ok: true,
      user_id: 'BOT_USER_ID',
      bot_id: 'BOT_ID',
    });

    Object.assign(this, instance);
    return this;
  },
);

I wasn’t sure if it would be OK for it no to be a class anymore, though. 🤷

Mr0grog commented 2 years ago

Worth noting: I haven’t tested this extensively with an example project, and it could probably benefit from a test matrix that covers multiple major releases of Jest and Slack.

barlock commented 2 years ago

Published in 1.4.0

Mr0grog commented 2 years ago

Sweet, just tried it out on the project where I was hitting and it works great with the latest Jest and Slack releases! 😄