-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[Uptime] Tests/uptime testing utils #87650
Conversation
Pinging @elastic/uptime (Team:uptime) |
40be0cd
to
afea632
Compare
); | ||
}; | ||
|
||
export const renderTLWithRouter = ( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" />, |
There was a problem hiding this comment.
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 } } }
.
There was a problem hiding this comment.
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 } } } |
There was a problem hiding this comment.
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; | ||
}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; | ||
}; |
There was a problem hiding this comment.
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}`)); |
There was a problem hiding this comment.
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', () => { |
There was a problem hiding this comment.
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 }; |
There was a problem hiding this comment.
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?
6182468
to
48f1536
Compare
@elasticmachine merge upstream |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
coreOptions?: any; | |
coreOptions?: Partial<CoreStart>; |
interface RenderRouterOptions extends KibanaProviderOptions { | ||
history?: History; | ||
renderOptions?: any; | ||
state?: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
state?: any; | |
state?: AppState; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
renderOptions?: any; | |
renderOptions?: Omit<RenderOptions, 'queries'>; |
const { asFragment } = render( | ||
<MLJobLink dateRange={{ to: '', from: '' }} basePath="" monitorId="testMonitor" />, | ||
{ | ||
coreOptions: { triggersActionsUi: { getEditAlertFlyout: jest.fn() } }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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" />, |
There was a problem hiding this comment.
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.
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are copied.
fe5c729
to
ae7406b
Compare
@@ -5,4 +5,4 @@ | |||
*/ | |||
|
|||
export { MountWithReduxProvider } from './helper'; | |||
export * from './helper/helper_with_router'; | |||
export * from './helper/enzyme_helpers'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! WFG
💚 Build SucceededMetrics [docs]
History
To update your PR or re-run it, just comment with: |
* 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>
* 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>
* 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>
* 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>
Summary
Fixes #87419
Adds testing additional testing utilities
Mock Provider Components
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.