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

[Enterprise Search] Automatically mock shared logic files #88494

Merged
merged 17 commits into from
Jan 15, 2021

Conversation

cee-chen
Copy link
Member

@cee-chen cee-chen commented Jan 15, 2021

Summary

Part 2 of my Jest cleanup: I've mentioned recently after seeing a ton of jest.mock('../../../shared/kibana') (or http, or flash_messages, etc.) cruft at the top of our logic files that it would be really nice to clean up / DRY out said mocks. We already do this for our React component tests via mocking useValues and useActions, but we didn't have this for our actual Kea logic tests that directly connected/grabbed values from other files (e.g. SomeLogic.value.someValue).

Well, I was yesterday years old when I learned that jest.mock() is relative to the test file, not to the actual file being tested... meaning that we can definitely DRY out mocking our logic. I decided to accomplish this by directly mocking each shared logic file in its __mocks__/*_logic.mock.ts (see 93b7242). This means that importing any mocks from our __mocks__ folder will automatically mock the logic file for you. Check out the following code diff example:

jest.mock('../../../shared/kibana', () => ({
  KibanaLogic: { values: { history: { location: { search: '' } } } },
}));
import { KibanaLogic } from '../../../shared/kibana';

jest.mock('../../../shared/http', () => ({
  HttpLogic: { values: { http: { get: jest.fn() } } },
}));
import { HttpLogic } from '../../../shared/http';

jest.mock('../../../shared/flash_messages', () => ({
  flashAPIErrors: jest.fn(),
}));
import { flashAPIErrors } from '../../../shared/flash_messages';

becomes:

import { mockKibanaValues, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__';

const { history } = mockKibanaValues;
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;

The latter is hopefully much cleaner and nicer to use - we shouldn't have to manually mock XLogic.values or YLogic.actions in the future, we just have to pull the existing mocked actions/values from our mock helpers. 🎉

Following along

I recommend following this PR by-commit since it touches so many different files.

A warning about this approach

It's a fairly "hungry" change. I wasn't super familiar with how imports worked previously (I thought you had to statically import the entire file to get the jest.mock() functionality), but I'm definitely more familiar now. So here's the warning:

⚠️ Any time that you import anything from the __mocks__ index folder, you are now automatically importing all the mocks for every single one of these shared logic files. ⚠️

For the most part, this shouldn't be a huge deal. Mocks are a way of life in unit testing unless we are attempting to unit test the logic file itself, in which case we have a few options:

  • Work around importing all mocks by importing only a very specific file within __mocks__, e.g. import specifically __mocks__/http_logic.mock which still retains the Kibana logic file as-is for example.
  • Import all the __mocks__, but retain importing the actual file by importing from the *_logic.ts file directly - e.g., import { HttpLogic } from 'shared/http/http_logic instead of shared/http (since our mocks are mocking the index.ts obj, not the actual files).

See b49b877 as an example of the folder most affected by this hungry mocking - because some of these files do require pulling from the __mocks__ folder to mock other logic/kea/etc, I had to change all test files to import the code they were testing directly from their source files to fix the tests.

Again, to reiterate, this should mostly only affect shared logic files, and I don't anticipate us adding a whole lot more of those. I think the tradeoff there (some extra consideration) vs 90% of the rest of the app gaining nicer dev QOL is worth it.

Checklist

- Caused by leaking auto mocks - work around this by importing from files directly and not from index.ts

- Also clean up / use new auto mocks (e.g. for kibana)

- Convert old instances of useValues mock to setMockValues
+ move LogicMounter and destructured mock values to top of describe block
+ udpate to use new clearFlashMessages helper
+ update to use LogicMounter
+ update to use new clearFlashMessages helper
+ update to use new flash message helpers
+ consolidate mocks imports
- all jest.mocks come along from the ride when the __mocks__ folder is imported, so HttpLogic is now automatically already mocked
@cee-chen cee-chen added Feature:Plugins release_note:skip Skip the PR/issue when compiling release notes v7.12.0 labels Jan 15, 2021
Comment on lines 21 to +26
describe('AnalyticsLogic', () => {
const { mount } = new LogicMounter(AnalyticsLogic);
const { history } = mockKibanaValues;
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;

Copy link
Member Author

@cee-chen cee-chen Jan 15, 2021

Choose a reason for hiding this comment

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

I decided to reorganize our tests a bit in the light of all these new fancy helpers. Here's the basic architecture I'm thinking:

/**
 * License header
 */

// Mocks go here
import { LogicMounter, mockHttpValues } from '../../__mocks__';

// 3rd party imports go here
import React from 'react';
import { shallow } from 'enzyme';

// 1st party shared/lib/helper imports go here
import { someHelper } from '../../../shared/helper';

// 1st party specific imports go here
import { SomeComponent } from '../';
import { SomeLogic } from './';

describe('SomeLogic', () => {
  // Destructuring from mocks go here - e.g., `mount` from LogicMounter
  const { mount, unmount } = new LogicMounter(SomeLogic);
  const { http } = mockHttpValues;

  // Constants (e.g.) values, actions go here
  const DEFAULT_VALUES = { ... };
  const foo = 'bar';

  // Jest before/after/all blocks
  beforeEach(() => {
    // ...
  });

  // Start the tests
  it('does something', () => {});
});

LMK what you think!

Copy link
Contributor

Choose a reason for hiding this comment

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

I like this, let's make sure it's in a README file somewhere in this codebase.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm you think so? Maybe I'm overly optimistic, but I'm hoping that we follow this by osmosis - I figure someone opening the codebase for the first time will be like "Oh all the unit tests are written this way, I'll just follow it/copy this file".

My only hesitation with putting this in our README is if it falls out of date, etc. That being said there's definitely a testing section in our main README, so if other folks are +1 to this I can definitely add it there!

Copy link
Member

Choose a reason for hiding this comment

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

I think I agree with ya there constance.

Copy link
Contributor

@byronhulcher byronhulcher left a comment

Choose a reason for hiding this comment

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

This looks good to me. I left a non-blocking comment about the value of allowing the import { mockKibanaValues, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__'; style import compared to the cost of debugging its side-effects in the future. Even if we decide to go in that direction we can handle it in a follow-up PR, it does not need to block the work already done here to improve our tests.

Comment on lines +12 to +16
export {
mockFlashMessagesValues,
mockFlashMessagesActions,
mockFlashMessageHelpers,
} from './flash_messages_logic.mock';
Copy link
Contributor

Choose a reason for hiding this comment

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

I've never really understood the value of creating these kinds of index.ts files for bundling unrelated code in sub-directories. That this introduces side-effects (if you import anything from this file everything gets mocked) seems REALLY weird, and is not something I would expect to encounter, and I imagine would be gnarly to debug. You mention a solution is to import these mocks individually in files, which you do for a couple of tests. Maybe we should handle all mock imports that way? Is there an upside to this I'm not seeing that's worth the side-effect?

Copy link
Member Author

@cee-chen cee-chen Jan 15, 2021

Choose a reason for hiding this comment

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

Ya it's definitely an architectural decision. As with everything it's all about dev tradeoffs, in my personal opinion I do strongly feel that it's nice to not have to dive deeply into folders - when I'm outside that folder but reaching into it from another component, it's very nice to just have everything available from the top-level index.ts file and not to have 3 different lines of imports for 3 different files.

It's also one that Kibana uses everywhere (example file) so I personally feel confident following them in this area. Should they ever change this (which I imagine would primarily be due to performance reasons, tree shaking, etc.), I would definitely be +1 to following suit.

I will also note that the side effects are limited to tests/jest.mock() behavior, whereas the benefits of quicker and combined imports apply outside those and to our source code as well (the main benefit IMO). For me at least the cost is well worth it once imports are understood - they were only unexpected to me because I didn't really understand how they worked. I strongly also think we'll find any side effects do not negatively impact like 95+% of our final code base.

Comment on lines 21 to +26
describe('AnalyticsLogic', () => {
const { mount } = new LogicMounter(AnalyticsLogic);
const { history } = mockKibanaValues;
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;

Copy link
Contributor

Choose a reason for hiding this comment

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

I like this, let's make sure it's in a README file somewhere in this codebase.

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
enterpriseSearch 1.8MB 1.8MB -95.0B

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

Copy link
Contributor

@scottybollinger scottybollinger 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 fantastic! Always great to see my personal todos done in someone else's PR 😂

Thanks Constance!

@JasonStoltz
Copy link
Member

So clean and nice. 100 ✋ high-five.

@cee-chen
Copy link
Member Author

Thanks y'all! I'll go ahead and merge this for now without a README update, but if y'all would like one, def feel free to open a PR or bug me about it later as a follow-up PR!

@cee-chen cee-chen merged commit 5b38816 into elastic:master Jan 15, 2021
@cee-chen cee-chen deleted the auto-jest-mocks branch January 15, 2021 20:39
scottybollinger added a commit to scottybollinger/kibana that referenced this pull request Jan 15, 2021
cee-chen pushed a commit that referenced this pull request Jan 15, 2021
…88530)

* Update shared logic mocks to automatically mock their exports

* Update FlashMessages to also mock its helper fns

* Fix tests that were relying on component exports from shared logic files

* Fix broken flash_messages tests

- Caused by leaking auto mocks - work around this by importing from files directly and not from index.ts

- Also clean up / use new auto mocks (e.g. for kibana)

- Convert old instances of useValues mock to setMockValues

* [AS] Update AnalyticsLogic test to use new auto mockers

+ move LogicMounter and destructured mock values to top of describe block

* [AS] Update CredentialsLogic

+ udpate to use new clearFlashMessages helper

* [AS] Update documents logic tests

* [AS] Update engines logic tests

* [AS] Update log retention logic test

* [Shared] Update IndexingStatus tests

+ update to use LogicMounter

* [Shared] Update telemetry logic test

* [WS] Update AddSourceLogic

+ update to use new clearFlashMessages helper

* [WS] Update groups logic files

+ update to use new flash message helpers

* [WS] Update OverviewLogic test

* [WS] Update AddSource component test

+ consolidate mocks imports

* [Shared] Clean up KibanaLogic imports

- all jest.mocks come along from the ride when the __mocks__ folder is imported, so HttpLogic is now automatically already mocked

* [AS] Update EngineRouter test
# Conflicts:
#	x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx
#	x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
#	x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jan 18, 2021
* master: (33 commits)
  [Security Solution][Case] Fix patch cases integration test with alerts (elastic#88311)
  [Security Solutions][Detection Engine] Removes duplicate API calls (elastic#88420)
  Fix log msg (elastic#88370)
  [Test] Add tag cloud visualization to dashboard in functional test for reporting (elastic#87600)
  removing kibana-core-ui from codeowners (elastic#88111)
  [Alerting] Migrate Event Log plugin to TS project references (elastic#81557)
  [Maps] fix zooming while drawing shape filter logs errors in console (elastic#88413)
  Porting fixes 1 (elastic#88477)
  [APM] Explicitly set environment for cross-service links (elastic#87481)
  chore(NA): remove mocha junit ci integrations (elastic#88129)
  [APM] Only display relevant sections for rum agent in service overview (elastic#88410)
  [Enterprise Search] Automatically mock shared logic files (elastic#88494)
  [APM] Disable Create custom link button on Transaction details page for read-only users
  [Docs] clean-up vega map reference documenation (elastic#88487)
  [Security Solution] Fix Timeline event details layout (elastic#88377)
  Change DELETE to POST for _bulk_delete to avoid incompatibility issues (elastic#87914)
  [Monitoring] Change cloud messaging on no data page (elastic#88375)
  [Uptime] clear ping state when PingList component in unmounted (elastic#88321)
  [APM] Consistent terminology for latency and throughput (elastic#88452)
  fix copy (elastic#88481)
  ...
scottybollinger added a commit that referenced this pull request Jan 18, 2021
* Initial copy/paste of component tree

Only does linting changes and lodash import

* Replace withRouter HOC with hooks

Use useLocation and no longer pass around history, using the KibanaLogic navigateToUrl method instead

* Migrate LicenseCallout component

* Update paths

Also change name of component to OauthApplication as the default import was named that before

* Remove conditional and passed in flash messages

This is no longer needed with the Kibana syntax. Flash messages are set globally and only render when present.

* Replace removed ConfirmModal

In Kibana, we use the Eui components directly

* Use internal tools for determining license

* Fix a bunch of type issues

* Remove telemetry settings section

We no longer control telemetry in Workplace Search. It is handled by Kibana itself

* Add SettingsSubNav component

* Add route and update nav

* Remove legacy AppView and sidenav

* Clear flash messages globally

* Remove global name change method call

The global org name is not used anywhere outside of this section so we no longer need to update the global organization object as it is re-fetched when this section is entered.

Previously, we displayed the org name in the sidebar but that has been removed in Kibana

* Refactor saveUpdatedConfig

We recently split out the logic from SourceLogic to the new AddSourceLogic and in settings, we were calling the saveSourceConfig method from the SourceLogic (now in AddSourceLogic) file and passing a callback that set the flash messages from that component’s state.

Now, we set the flash messages globally and no longer need to have a saveUpdatedConfig method in the logic file, but can call it directly in the component and the flash messages will be set globally.

* Update logic file to use global flash messages

* Update server routes

* Replace Rails http with kibana http

* Fix subnav

* Update routes to use consistent syntax

We use this method across both Enterprise Search products in Kibana

* Shorten nav item copy

This would be the only place in the sidebar with a nav item breaking to a second line.

* Fix some random typos

* Replace React Router Link with helper

* Add i18n

* Remove redundant clearing of flash messages

This happens automatically now in the global flash messages logic; route changes trigger clearing of messages

* Add unit tests for components

* Add tests for router

* Store oauthApplication in mock for reuse

* Add tests for SettingsLogic

* Fix typo

* Remove unncessary imports

Copied from this PR:
#88525

* Refactor to use new helpers when mocking

See #88494

* Update logic test to use error helper

See #88422

* Fix type issue

* Fix whitespace lint issue

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
@yakhinvadim yakhinvadim mentioned this pull request Jan 29, 2021
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Plugins release_note:skip Skip the PR/issue when compiling release notes v7.12.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants