Skip to content

Instantly share code, notes, and snippets.

@MrJadaml
Last active August 21, 2020 14:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MrJadaml/94e28fa2f4a3a6fbba3e3f20b98636e1 to your computer and use it in GitHub Desktop.
Save MrJadaml/94e28fa2f4a3a6fbba3e3f20b98636e1 to your computer and use it in GitHub Desktop.
RFC to change the mocks module convention.

Summary

Mocks are designated by a .mocks.js extensions and would be co-located with their corresponding module.

Basic example

├── SomeComponent
  ├── __snapshots__/
  ├── SomeComponent.scss
  ├── SomeComponent.js
  ├── SomeComponent.test.js
  ├── SomeComponent.mocks.js
// SomeComponent.mocks.js

// data for default use case
export const goodCat = {
  name: 'Moon',
  isCuddler: true
  isCounterSurffer: false
}

// data for conditional case
export const naughtyCat = {
  name: 'Apollo',
  isCuddler: false,
  isCounterSurffer: true
}

// explict data composition
export const cats = [ goodCat, naughtyCat ];

export const cutePuppy = {
  name: 'Bronson',
  isCuddler: true,
}

// data not to be included with required data but to be tested
export unrequiredDogs = [ cutePuppy ];

// minimum data required not to error -- provides an explicit understanding of the component's data contract
export const required = {
  i: 'must',
  have: cats,
  to: function()
}

Motivation

After spending what felt like an unnecessary amount of time rummaging through monolithic mock data sets -- 95% of which was unrelated or not required to produce passing tests -- I broke out only the minimum required data I needed to get my specs to pass.

The other shortfall with the current implementation was having a harder time . Given all the extra noise generated by irrelevant data to be parse though, building up a mental model of the problem at hand also became more cumbersome.

Combined that with common interruptions of meetings, slack conversation, lunch/EOD etc. that resulting in losing that mental model and it becomes clear how nice it is to keep those simple to repeatedly spin them back up again.

I also found it hard to know if the data for the use case I wanted already existed or if I should extend the mock to include it. There was a lack of clarity as to if I would either be breaking a dependency to data I was changing or if I were duplicating data that already existed.

Not even close to the worse offender, but what I was working in was +3K lines of mock data: Current story analytics mock

Not a perfect example as to what is laid out in the proposal, but a close example from my latest PR: NCE-234

File and organizational motivations:

The current __mocks naming pattern is similar to both suggesting a sort of "private" module as per the underscore prefix, though I would think this not a private module.

The current naming pattern is also similar to conventions used by other libraries with the ___mocks__ which adds some confusion, particularly since those patterns are also used in the ecom app by things like jest mock functions/files and snapshots. Those mocks are also intended to be "private" sending something of a mixed message.

The current __mocks directory does not reflect any sort of pattern for how the mocks are organized. Some live under the /components directory while some have their own directories at the top level, but then some mocks are not under any sub directory and the module live at the top level.

The proposed naming style follows the existing pattern used for test modules. Since those same test modules are the primary consumer of the mocks module having a matching pattern seems logical

The proposal of co-locating mocks within the same directory as the feature and test module they support seems like a convenient and intuitive approach. It will be very clear where to find and document data specific to that feature.

The proposed naming pattern also lends itself to potential devops tooling down the line.

The proposed co-location pattern lends itself to easy, confident and comprehensive clean-up in the case of a feature/module removal -- Mock data for a removed module won’t turn into dead code lost in large shared mocks located elsewhere in the app.

Module implementation motivations:

The current implementation of mocks tend to be monolithic data structures that are not always easy to navigate. When test coverage for existing untested functionality -- of which there is a fair amount of -- or new functionality.

The current mocks and not always easy to extend either due to their monolithic structure or the lack of clarity of other dependencies of that mock.

The current monolithic and dependency issues tends to drive extension by duplication only exacerbating both the monolithic and clarity issues.

Current mocks appear to have an excess of irrelevant data -- though it is hard to say as it relates to the lack of clarity around the dependencies previously mentioned.

Current mocks do not lend themselves to composition. Not necessarily a "flaw" so much as a missed opportunity.

Current mocks have mixed indentation patterns -- likely a product of copy and pasting in blocks of data grabbed from elsewhere.

The proposed implementation -- by nature of being feature oriented -- would lend itself to be more abbreviated data structures. Ideally one would not add data not declared via function-signature/propTypes for that util/component.

  • The co-location of the mocks also drives a greater sense of relation and maintenance, preventing abuse by way of dumping huge data structures with unrelated fields.

The co-location makes dependencies of the mock clear - easier to debug conflicts or extend.

The smaller scope allows mock data can be more easily broken down into more semantically named data sets tieing to specific use cases.

The smaller scope allows mock data to be composable in an explicit manner -- see example above.

The proposed implementation makes clean up easy if you delete a no longer relevant unit test. No large, implicit, catch-all data objects passed in during initial setup.

Drawbacks

  • We would be maintaining two patterns for a while, while the existing pattern is slowly deprecated
  • Alternatively, to not maintain two patterns there would be an upfront cost to migrating existing mocks to fit the new pattern.

Alternatives

An alternative would be to define mock data within the test modules themselves -- I would prefer the existing pattern over that though.

Adoption strategy

  • We could take an incremental strategy similar to how we approach adding new test coverage for areas of the codebase we touch.
  • There could also be an initial cleanup of "low hanging fruit" mocks that require minimal effort to migrate upfront.
  • There could then be a "wrap-up" phase down the line where it is clear the dependency requirement on the exiting pattern is mostly gone.

How we teach this

What names and terminology work best for these concepts and why?

  • Feature mocks: The full set of mock data associated to a specific feature.
  • Unit mocks: "Bare minimum" mock data associated to a specific unit test.
  • Required mock: The bare minimum data required in order for the feature to run without failing -- AKA required propTypes.

Unresolved questions

Would this also make sense for util/libs without causing unnecessary modularization?

Would there be a sensible way to "import" in higher level data that may be shared by multiple modules? If so, what are some examples?

Would there be a preference to a JSON format or a JS format?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment