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

[Uptime] Tests/uptime testing utils #87650

Merged

Conversation

dominiqueclarke
Copy link
Contributor

@dominiqueclarke dominiqueclarke commented Jan 7, 2021

Summary

Fixes #87419

Adds testing additional testing utilities

Mock Provider Components

  • MockKibanaProvider - a component that wraps children in KibanaContextProvider and 118nProvider
  • MockRouter - a component that wraps children in Router, in addition to KibanaContextProvider and 118nProvider

React testing library custom renders
renderTLWithKibana - a render function that takes a React Element and renders it using react testing library, wrapped in KibanaContextProvider and 118nProvider
renderTLWithRouter - a render function that takes a React Element and renders it using react testing library, wrapped in Router, in addition to KibanaContextProvider and 118nProvider

The idea is that if you don't need a router, you can use one of the Kibana only mock components or custom renders. The assumption is made that if you need a router, you probably need the KibanaContextProvider as well.

Some tests were changed as examples.

@dominiqueclarke dominiqueclarke requested a review from a team as a code owner January 7, 2021 15:11
@botelastic botelastic bot added the Team:Uptime - DEPRECATED Synthetics & RUM sub-team of Application Observability label Jan 7, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/uptime (Team:uptime)

@dominiqueclarke dominiqueclarke added v7.11.0 v8.0.0 enhancement New value added to drive a business result release_note:skip Skip the PR/issue when compiling release notes labels Jan 7, 2021
@dominiqueclarke dominiqueclarke marked this pull request as draft January 7, 2021 15:12
);
};

export const renderTLWithRouter = (
Copy link
Contributor

Choose a reason for hiding this comment

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

my initial though is that, imo, i don't think we should name it like this renderWithRouter or renderWithKibana stuff.

I wish we can find a better naming? maybe just have it as simple render, and by default we should add kibana, router and redux for all components, since most components in uptime are dependent those.

I don't think we will have a use case where adding those by default will cause problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The one reason I didn't name it renderWithRouter is because we already have a renderWithRouter in this same file that we are using for enzyme.

I will consider consolidating into one render function.

<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />
</KibanaContextProvider>
const { asFragment } = renderTLWithRouter(
<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an example where we needed to extend the core options. You can do so by passing customCoreOptions. If you need to overwrite the entire core and make your own mock, you can do so by passing { kibanaProps: { services: { ...myCustomCore } } }.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unable to figure out why this component needed to call getEditAlertFlyout. The MLJobLink component sets up an href and a few other fields and renders a link.

<Component />
</Route>
</MountWithReduxProvider>,
{ kibanaProps: { services: { ...core } } }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an example of overwriting the entire Kibana context core. This is useful in this context to get access to a function for getting the breadcrumbs out of core.

};

return core;
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to create a mock core that met most of our use cases without having to add to it, but I don't have the context to know what that looks like. I would love feedback here.

Copy link
Contributor

Choose a reason for hiding this comment

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

TBH this might be something we need to take a couple of attempts at and incrementally improve it as we run into different issues. I think we can try to review the fields we use to a certain extent but it's ok if we need to revisit the default mock.

}: {
customHistory?: History;
customCoreOptions?: any;
kibanaProps?: { services: any };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what the types should be for some of these pieces. I welcome feedback here.

Copy link
Contributor

Choose a reason for hiding this comment

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

let's remove the custom prefix, i think that's unnecessary

Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the types, I believe Kibana Core team exports types for core that we can use here. See https://github.com/elastic/kibana/blob/master/x-pack/plugins/uptime/public/apps/plugin.ts#L7

cc @shahzad31 does that sound right?

Copy link
Contributor

Choose a reason for hiding this comment

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

i think there might be a kibana core mock as well exported by team, may be use that directly?

Copy link
Contributor

Choose a reason for hiding this comment

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

i have seen that somewhere

Copy link
Contributor

Choose a reason for hiding this comment

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

@dominiqueclarke might be worth asking in #kibana-developer-experience channel

);
expect(wrapper).toMatchSnapshot();
expect(asFragment()).toMatchSnapshot();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I only changes this to useasFragment because I don't have further context about this component to know how to test it without a snapshot. I'm using it as an example for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can improve this even more and remove the need for asFragment.

What do you think of 9728d3a? We're essentially testing the user-facing features of the component; specifically we ensure our text is displayed and the href we build is what we expect. As a bonus we can eliminate the snapshot file altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's perfect. I wasn't sure how to format that full url lol.

customCoreOptions,
kibanaProps,
}: {
children: ReactElement;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you planning to define an interface rather than specifying the types inline like this? These Mock functions have a lot of parameter overlap.

};

return core;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

TBH this might be something we need to take a couple of attempts at and incrementally improve it as we run into different issues. I think we can try to review the fields we use to a certain extent but it's ok if we need to revisit the default mock.

<ExecutedStep index={3} step={step} checkGroup={'fake-group'} />
);

expect(getByText(`${step?.synthetics?.step?.index}. ${step?.synthetics?.step?.name}`));
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 exactly what I was hoping to achieve when we talked about this test. It tests the same thing in a much cleaner format.


// FLAKY: https://github.com/elastic/kibana/issues/85899
describe.skip('ExecutedStep', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to cherry-pick this change to a separate branch and revert it here, since we're going to need to get approval to close the issue before we can remove the skip. I'm fine with keeping the test revision below since they're all skipped in master right now anyway.

}: {
customHistory?: History;
customCoreOptions?: any;
kibanaProps?: { services: any };
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the types, I believe Kibana Core team exports types for core that we can use here. See https://github.com/elastic/kibana/blob/master/x-pack/plugins/uptime/public/apps/plugin.ts#L7

cc @shahzad31 does that sound right?

@dominiqueclarke dominiqueclarke marked this pull request as ready for review January 7, 2021 20:39
@dominiqueclarke
Copy link
Contributor Author

@elasticmachine merge upstream

Copy link
Contributor

@justinkambic justinkambic left a comment

Choose a reason for hiding this comment

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

This is looking great, and I've already merged these commits into some other testing that I'm doing and everything went well. I had a few recommendations on typings etc, and some possible improvements to one of the example tests we're touching here.

I checked out this code and made two commits, 9728d3a and 8e35b6b. They summarize most of my revision comments below. Ping with any follow-up.

Thanks for taking this on, it's going to be a big help going forward!

}

export function withRouter<T>(WrappedComponent: React.ComponentType<T>, customHistory: History) {
const history = customHistory || createMemoryHistory();
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like customHistory should be optional since we have a default if it is falsey.

);
expect(wrapper).toMatchSnapshot();
expect(asFragment()).toMatchSnapshot();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can improve this even more and remove the need for asFragment.

What do you think of 9728d3a? We're essentially testing the user-facing features of the component; specifically we ensure our text is displayed and the href we build is what we expect. As a bonus we can eliminate the snapshot file altogether.

@@ -5,28 +5,17 @@
*/

import React from 'react';
import { coreMock } from 'src/core/public/mocks';
import { renderWithRouter, shallowWithRouter } from '../../../lib';
import { render } from '../../../lib';
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing I noticed in practice when testing this out was my editor had some issue loading the typings for this function. Is this happening for you? When I edited the path to do a direct import it worked. I can't figure out why this would be a code-related problem, but I'm curious if it's happening with others.

Specifically, I didn't get any intellisense in VS Code for the asFragment function returned by render.

We've had some issues with these types of import chains in the past. Not saying we should mandate always doing direct imports for this helper, but it might be helpful for some.

);
}

export function withRouter<T>(WrappedComponent: React.ComponentType<T>, customHistory: History) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be good to seek out a test where this HoC can be used and provide an example. I think right now it returns JSX.Element, if we're intending for it to be passed to RTL render directly we might want to make sure the inferred return type is ReactElement. If someone finds this HoC it would be ideal for them to Find all references and see it in action somewhere in the codebase, IMO.

In any case, I think it's rare that we'll need to call this specifically, as the custom render you've created below should cover the vast majority of use cases and is simpler to interface with.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My original idea of using HOCs was a bit misguided, but I left them here in case they are helpful for anyone in any other contexts.

I used to work with RTL using HOCs. I would first create the wrapped component (JSX.Element), and then pass the ReactElement to RTL like so

const WrappedComponent = withRouter(Component, history);
render(<WrappedComponent {...props} />);

But that just creates one more unnecessary step.

All in all I think these can be removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see; I'm inclined to agree if we don't have a need for them today. If we run into an instance where using HOCs is preferable, it seems like it will be a small implementation effort to re-add them.

ui: ReactElement,
{ history, coreOptions, kibanaProps, renderOptions, state }: RenderRouterOptions = {}
) => {
return reactRender(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should alias this as reactTestRender to denote it's calling render imported from RTL and not react-dom.

I think if the reader understands the context of this file it's probably not necessary, but some more descriptive name like that (or a better suggestion) might improve readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should name it something other than reactTestRender since react test renderer is a thing that it may be confused with. How about reactTestLibRender

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds fine to me. This way it will disambiguate the source of the function for the person reading the code.

}

interface KibanaProviderOptions {
coreOptions?: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we able to reference CoreStart here? As I understand it we use this field to let people supply their own coreOptions, which we spread overtop of the default mock we create. So could we type this as Partial<CoreStart>, so the caller could supply just one or two fields if needed, or a full mock if they wanted?

Suggested change
coreOptions?: any;
coreOptions?: Partial<CoreStart>;

interface RenderRouterOptions extends KibanaProviderOptions {
history?: History;
renderOptions?: any;
state?: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
state?: any;
state?: AppState;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The one thing I don't love about requiring the full app state, is that you have to provide pieces of state that are unnecessary to the particular component you are testing in order to satisfy the interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we could use generics for this and specify the smaller slice of state you need.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point; can we use Partial<AppState> instead, and when we

<MountWithReduxProvider store={store}>

change it to:

<MountWithReduxProvider store={...mockStore, ...userStore}>


interface RenderRouterOptions extends KibanaProviderOptions {
history?: History;
renderOptions?: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should require a type of Omit<RenderOptions, 'queries'>, mimicking the render function that RTL exports.

Suggested change
renderOptions?: any;
renderOptions?: Omit<RenderOptions, 'queries'>;

const { asFragment } = render(
<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />,
{
coreOptions: { triggersActionsUi: { getEditAlertFlyout: jest.fn() } },
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
coreOptions: { triggersActionsUi: { getEditAlertFlyout: jest.fn() } },

I think the test will succeed without this when we migrate to RTL. See revision in 8e35b6b.

<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />
</KibanaContextProvider>
const { asFragment } = renderTLWithRouter(
<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unable to figure out why this component needed to call getEditAlertFlyout. The MLJobLink component sets up an href and a few other fields and renders a link.

Copy link
Contributor Author

@dominiqueclarke dominiqueclarke left a comment

Choose a reason for hiding this comment

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

Thanks for the feedback! One note about AppState, I'd love to get around requiring the full AppState for redux tests if possible.

);
expect(wrapper).toMatchSnapshot();
expect(asFragment()).toMatchSnapshot();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's perfect. I wasn't sure how to format that full url lol.

interface RenderRouterOptions extends KibanaProviderOptions {
history?: History;
renderOptions?: any;
state?: any;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The one thing I don't love about requiring the full app state, is that you have to provide pieces of state that are unnecessary to the particular component you are testing in order to satisfy the interface.

);
}

export function withRouter<T>(WrappedComponent: React.ComponentType<T>, customHistory: History) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

My original idea of using HOCs was a bit misguided, but I left them here in case they are helpful for anyone in any other contexts.

I used to work with RTL using HOCs. I would first create the wrapped component (JSX.Element), and then pass the ReactElement to RTL like so

const WrappedComponent = withRouter(Component, history);
render(<WrappedComponent {...props} />);

But that just creates one more unnecessary step.

All in all I think these can be removed.

);
};

/* Enzyme helpers */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are copied.

@@ -5,4 +5,4 @@
*/

export { MountWithReduxProvider } from './helper';
export * from './helper/helper_with_router';
export * from './helper/enzyme_helpers';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exported the enzyme helpers from here in order to maintain compatibility with the existing tests without having to change the imports.

Copy link
Contributor

@justinkambic justinkambic left a comment

Choose a reason for hiding this comment

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

LGTM! WFG

@dominiqueclarke dominiqueclarke changed the title Tests/uptime testing utils [Uptime] Tests/uptime testing utils Jan 11, 2021
@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@dominiqueclarke dominiqueclarke merged commit ca31bd8 into elastic:master Jan 12, 2021
@dominiqueclarke dominiqueclarke deleted the tests/uptime-testing-utils branch January 12, 2021 01:02
dominiqueclarke added a commit to dominiqueclarke/kibana that referenced this pull request Jan 12, 2021
* add additional component test helpers

* add test examples

* uptime testing utils remove custom prefix from props and parameter options

* skip executed step tests

* adjust MlJobLink test

* add testing util interfaces

* update mock core

* combine wrappers into one custom render function

* split enzyme helpers and rtl helpers into different files and adjust types

* adjust types

* spread core on render function

* remove unnecessary items from MLJobLink test

* update use_monitor_breadcrumbs test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
dominiqueclarke added a commit to dominiqueclarke/kibana that referenced this pull request Jan 12, 2021
* add additional component test helpers

* add test examples

* uptime testing utils remove custom prefix from props and parameter options

* skip executed step tests

* adjust MlJobLink test

* add testing util interfaces

* update mock core

* combine wrappers into one custom render function

* split enzyme helpers and rtl helpers into different files and adjust types

* adjust types

* spread core on render function

* remove unnecessary items from MLJobLink test

* update use_monitor_breadcrumbs test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
dominiqueclarke added a commit that referenced this pull request Jan 12, 2021
* add additional component test helpers

* add test examples

* uptime testing utils remove custom prefix from props and parameter options

* skip executed step tests

* adjust MlJobLink test

* add testing util interfaces

* update mock core

* combine wrappers into one custom render function

* split enzyme helpers and rtl helpers into different files and adjust types

* adjust types

* spread core on render function

* remove unnecessary items from MLJobLink test

* update use_monitor_breadcrumbs test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
dominiqueclarke added a commit that referenced this pull request Jan 12, 2021
* add additional component test helpers

* add test examples

* uptime testing utils remove custom prefix from props and parameter options

* skip executed step tests

* adjust MlJobLink test

* add testing util interfaces

* update mock core

* combine wrappers into one custom render function

* split enzyme helpers and rtl helpers into different files and adjust types

* adjust types

* spread core on render function

* remove unnecessary items from MLJobLink test

* update use_monitor_breadcrumbs test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New value added to drive a business result release_note:skip Skip the PR/issue when compiling release notes Team:Uptime - DEPRECATED Synthetics & RUM sub-team of Application Observability v7.11.0 v7.12.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Uptime] Create generic testing utilities
5 participants