Skip to content

Instantly share code, notes, and snippets.

@devinschumacher
Last active November 14, 2024 12:41
Show Gist options
  • Save devinschumacher/f585634b362da6ea1b0eaf753d877ee2 to your computer and use it in GitHub Desktop.
Save devinschumacher/f585634b362da6ea1b0eaf753d877ee2 to your computer and use it in GitHub Desktop.
Architecture, Setup & Config for: Nuxt3 + vitest + @nuxt/test-utils + typescript

image


image


image


image

  • if youre asserting that something DOESNT exist in the DOM, youll want to use queryBy

image


image


Watch this video for more explanation + examples of "real tests"

What to use & when

Jest & vitest have the same apis almost exact

  1. Test Structure:

    • describe()
    • it()/test()
    • describe.skip()/it.skip()
    • describe.only()/it.only()
    • test.todo()
    • test.concurrent()
    • test.each()
    • describe.each()
  2. Test Lifecycle:

    • beforeAll()
    • beforeEach()
    • afterEach()
    • afterAll()
  3. Mocking:

    • jest.fn()/vi.fn()
    • jest.spyOn()/vi.spyOn()
    • jest.mock()/vi.mock()
    • mockImplementation()
    • mockReturnValue()
    • mockResolvedValue()
    • mockRejectedValue()
  4. Timer Mocks:

    • jest.useFakeTimers()/vi.useFakeTimers()
    • jest.runAllTimers()/vi.runAllTimers()
    • jest.advanceTimersByTime()/vi.advanceTimersByTime()
  5. Assertions (expect()): General:

    • .toBe()
    • .toEqual()
    • .toStrictEqual()
    • .toBeTruthy()
    • .toBeFalsy()
    • .toBeNull()
    • .toBeUndefined()
    • .toBeDefined()

    Numbers:

    • .toBeGreaterThan()
    • .toBeGreaterThanOrEqual()
    • .toBeLessThan()
    • .toBeLessThanOrEqual()
    • .toBeCloseTo()

    Strings:

    • .toMatch()
    • .toContain()
    • .toHaveLength()
    • .toMatchSnapshot()

    Objects/Arrays:

    • .toHaveProperty()
    • .toMatchObject()
    • .toContainEqual()
    • .toHaveLength()

    Functions/Promises:

    • .toThrow()
    • .toHaveBeenCalled()
    • .toHaveBeenCalledWith()
    • .toHaveBeenCalledTimes()
    • .resolves
    • .rejects
  1. Mounting:

    • mount()
    • shallowMount()

    Mount Options:

    • attachTo
    • attrs
    • data
    • props
    • slots
    • scopedSlots
    • global
      • global.components
      • global.directives
      • global.mocks
      • global.provide
      • global.plugins
      • global.stubs
    • shallow
  2. Wrapper Methods (Interaction):

    • .setData()
    • .setProps()
    • .setValue()
    • .trigger()
    • .setMethods()
    • .setChecked()
    • .setSelected()
  3. Wrapper Methods (Finding/Querying):

    • .find()
    • .findAll()
    • .findComponent()
    • .findAllComponents()
    • .get()
    • .getComponent()
    • .filter()
  4. Wrapper Methods (Inspection):

    • .attributes()
    • .classes()
    • .emitted()
    • .exists()
    • .html()
    • .isVisible()
    • .props()
    • .text()
    • .vm
    • .element
    • .isEmpty()
    • .isVueInstance()
  5. Cleanup/Utility:

    • .unmount()
    • enableAutoUnmount()
    • flushPromises()
    • config.global
    • config.plugins
    • RouterLinkStub

TESTING LIBRARY VUE SYNTAX

  1. Rendering:

    • render()
    • screen
    • cleanup()
    • within()
    • renderHook()
  2. Queries (for each: getBy, queryBy, findBy, getAllBy, queryAllBy, findAllBy):

    • ByRole()
    • ByLabelText()
    • ByPlaceholderText()
    • ByText()
    • ByDisplayValue()
    • ByAltText()
    • ByTitle()
    • ByTestId()
  3. User Events:

    • userEvent.setup()
    • userEvent.click()
    • userEvent.dblClick()
    • userEvent.type()
    • userEvent.keyboard()
    • userEvent.upload()
    • userEvent.clear()
    • userEvent.selectOptions()
    • userEvent.deselectOptions()
    • userEvent.tab()
    • userEvent.hover()
    • userEvent.unhover()
  4. Fire Events:

    • fireEvent.click()
    • fireEvent.change()
    • fireEvent.submit()
    • fireEvent.keyDown()
    • fireEvent.keyUp()
    • fireEvent.keyPress()
tags
nuxt
testing
vitest
test-utils

JS Architecture for: Nuxt3 w/ vitest + @nuxt/test-utils

Layer Description Example(s)
Test Library Highest-level APIs for testing. Provides user-friendly utilities for rendering components, querying the DOM, and simulating user interactions. Includes syntax like screen.getByText() and assertions like toBeInTheDocument(). @testing-library/vue, @testing-library/dom
Test Utils Framework-specific testing helpers. Provides Nuxt-specific helpers for mounting and rendering components, as well as Vue-specific utilities. Includes methods like renderSuspended() (Testing Library syntax) and mountSuspended() (Vue Test Utils syntax). @nuxt/test-utils, @vue/test-utils
DOM Renderer Virtual DOM implementation. Simulates a DOM environment for tests to run in, enabling DOM interactions and component rendering. Supports DOM-related assertions and interactions. happy-dom, jsdom
Test Runner Test execution engine. Manages test file execution, provides assertion APIs, and defines test case syntax (e.g., it(), expect()). vitest, Jest
JS Build Tool Code transformation layer. Handles bundling, compilation, and transformation of test files. Processes source code, manages imports, and may provide features like HMR during development. Vite, esbuild, webpack
JS Runtime Code execution environment. Provides the actual JavaScript execution environment, handling module resolution, async operations, and timers. Node.js, Browser
Package Manager Foundation layer. Handles dependency resolution, installation, and script execution. Maintains deterministic builds through lock files and provides the CLI interface for running tests and other scripts. npm, yarn, pnpm, bun

Typical Flow (Bottom to Top)

  1. Package Manager (e.g., yarn)

    • Installs dependencies
    • Manages versions
    • Provides script interface
  2. JavaScript Runtime (e.g., Node.js)

    • Executes JavaScript code
    • Handles module resolution
  3. Build Tool (e.g., Vite)

    • Bundles test files
    • Processes imports
    • Handles TypeScript/JSX
  4. Test Runner (e.g., Vitest)

    • Executes test files
    • Provides assertion APIs
  5. DOM Renderer (e.g., happy-dom)

    • Simulates browser environment
    • Handles DOM operations
  6. Test Utils (@nuxt/test-utils)

    • Provides framework-specific helpers
    • Handles Nuxt-specific features
  7. Test Library (@testing-library/vue)

    • Provides high-level testing APIs
    • Enables user-centric testing

Install-Setup-Config-Use paths for the stack

A chart to outline the setup options for testing in your Nuxt 3 + Vitest + Nuxt Test Utils + TypeScript stack, covering the four potential paths.

Path DOM Renderer Test Library Install Setup/Config Primary Testing Methods
Path 1 happy-dom Vue Test Utils npm install -D @nuxt/test-utils vitest @vue/test-utils happy-dom In vitest.config.ts, set environment: 'happy-dom' Use mountSuspended() to mount components. Use @vue/test-utils methods like find(), trigger(), etc.
Path 2 jsdom Vue Test Utils npm install -D @nuxt/test-utils vitest @vue/test-utils jsdom In vitest.config.ts, set environment: 'jsdom' Same as Path 1, but with jsdom for broader compatibility if needed for complex DOM scenarios.
Path 3 happy-dom Testing Library npm install -D @nuxt/test-utils vitest @testing-library/vue happy-dom @testing-library/jest-dom In vitest.config.ts, set environment: 'happy-dom'. Import @testing-library/jest-dom for extended matchers. Use renderSuspended() to render components. Use @testing-library/vue methods like screen.getByText(), toBeInTheDocument().
Path 4 jsdom Testing Library npm install -D @nuxt/test-utils vitest @testing-library/vue jsdom @testing-library/jest-dom In vitest.config.ts, set environment: 'jsdom'. Import @testing-library/jest-dom for extended matchers. Same as Path 3, but with jsdom for complex DOM compatibility in user-centered tests.

Explanation of Each Column

  • DOM Renderer: Choose between happy-dom (faster) and jsdom (more compatible with complex DOM requirements).
  • Test Library: Choose between Vue Test Utils (implementation-focused) and Testing Library (user-centric).
  • Install: These are the core dependencies you’ll need for each path.
  • Setup/Config: This includes the key configuration settings in vitest.config.ts to define the test environment.
  • Primary Testing Methods: Suggested testing methods that best align with each path.

Additional Notes

  • For TypeScript support, make sure to enable types in tsconfig.json for any custom types you need.
  • @testing-library/jest-dom provides additional matchers (like toBeInTheDocument()) for Testing Library paths.

Refer to API references for the specific testing library you’re using. Here’s a breakdown of the relevant references for each library and path in your setup:

1. Vue Test Utils (@vue/test-utils) API Reference

  • Path 1 and Path 2: If you're using Vue Test Utils with mountSuspended(), you’ll want to reference the Vue Test Utils API documentation.
  • Documentation: Vue Test Utils API
  • Methods: Common methods include mount(), find(), findComponent(), setData(), trigger(), etc.
  • Purpose: This library is more implementation-focused, allowing you to test internal component states, props, and lifecycle events.

2. Testing Library for Vue (@testing-library/vue) API Reference

  • Path 3 and Path 4: If you're using Testing Library with renderSuspended(), refer to Testing Library’s Vue API for user-centric testing.
  • Documentation: Testing Library for Vue
  • Methods: Core methods include render(), screen.getByText(), screen.queryByTestId(), and userEvent for simulating user interactions.
  • Matchers: Extendable with @testing-library/jest-dom for matchers like toBeInTheDocument(), toHaveClass(), etc.
  • Purpose: This library focuses on testing component behavior from a user’s perspective, helping you validate what users see and interact with.

3. Vitest API Reference

  • All Paths: Since Vitest is your test runner, you’ll use its API for setting up test cases, expectations, and matchers.
  • Documentation: Vitest API
  • Methods: Key methods include describe(), it(), expect(), beforeEach(), afterEach(), etc. The API is similar to Jest’s, so it's easy to adapt if you're familiar with Jest.
  • Matchers: Standard matchers are built-in, but you can add matchers from @testing-library/jest-dom if you’re using Testing Library.

4. TypeScript Definitions

  • All Paths: If you’re working with TypeScript, make sure you have type definitions installed for each library (e.g., @types/testing-library__jest-dom for jest-dom matchers).
  • Setup: Ensure that your tsconfig.json includes types for any test libraries you’re using to get autocompletion and type checking in your .test.ts files.

API Reference

chai assertion library

docs: chai assertion

assert

  • fail
  • isOk
  • isNotOk
  • equal
  • notEqual
  • strictEqual
  • notStrictEqual
  • deepEqual
  • notDeepEqual
  • isAbove
  • isAtLeast
  • isBelow
  • isAtMost
  • isTrue
  • isNotTrue
  • isFalse
  • isNotFalse
  • isNull
  • isNotNull
  • isNaN
  • isNotNaN
  • exists
  • notExists
  • isUndefined
  • isDefined
  • isFunction
  • isNotFunction
  • isObject
  • isNotObject
  • isArray
  • isNotArray
  • isString
  • isNotString
  • isNumber
  • isNotNumber
  • isFinite
  • isBoolean
  • isNotBoolean
  • typeOf
  • notTypeOf
  • instanceOf
  • notInstanceOf
  • include
  • notInclude
  • deepInclude
  • notDeepInclude
  • nestedInclude
  • notNestedInclude
  • deepNestedInclude
  • notDeepNestedInclude
  • ownInclude
  • notOwnInclude
  • deepOwnInclude
  • notDeepOwnInclude
  • match
  • notMatch
  • property
  • notProperty
  • propertyVal
  • notPropertyVal
  • deepPropertyVal
  • notDeepPropertyVal
  • nestedProperty
  • notNestedProperty
  • nestedPropertyVal
  • notNestedPropertyVal
  • deepNestedPropertyVal
  • notDeepNestedPropertyVal
  • lengthOf
  • hasAnyKeys
  • hasAllKeys
  • containsAllKeys
  • doesNotHaveAnyKeys
  • doesNotHaveAllKeys
  • hasAnyDeepKeys
  • hasAllDeepKeys
  • containsAllDeepKeys
  • doesNotHaveAnyDeepKeys
  • doesNotHaveAllDeepKeys
  • throws
  • doesNotThrow
  • operator
  • closeTo
  • approximately
  • sameMembers
  • notSameMembers
  • sameDeepMembers
  • notSameDeepMembers
  • sameOrderedMembers
  • notSameOrderedMembers
  • sameDeepOrderedMembers
  • notSameDeepOrderedMembers
  • includeMembers
  • notIncludeMembers
  • includeDeepMembers
  • notIncludeDeepMembers
  • includeOrderedMembers
  • notIncludeOrderedMembers
  • includeDeepOrderedMembers
  • notIncludeDeepOrderedMembers
  • oneOf
  • changes
  • changesBy
  • doesNotChange
  • changesButNotBy
  • increases
  • increasesBy
  • doesNotIncrease
  • increasesButNotBy
  • decreases
  • decreasesBy
  • doesNotDecrease
  • doesNotDecreaseBy
  • decreasesButNotBy
  • ifError
  • isExtensible
  • isNotExtensible
  • isSealed
  • isNotSealed
  • isFrozen
  • isNotFrozen
  • isEmpty
  • isNotEmpty

expect and should

  • not
  • deep
  • nested
  • own
  • ordered
  • any
  • all
  • a
  • include
  • ok
  • true
  • false
  • null
  • undefined
  • NaN
  • exist
  • empty
  • arguments
  • equal
  • eql
  • above
  • least
  • below
  • most
  • within
  • instanceof
  • property
  • ownPropertyDescriptor
  • lengthOf
  • match
  • string
  • keys
  • throw
  • respondTo
  • itself
  • satisfy
  • closeTo
  • members
  • oneOf
  • change
  • increase
  • decrease
  • by
  • extensible
  • sealed
  • frozen
  • finite
  • fail
  • fail

vitest

docs: vitest

  • test
    • test.extend
    • test.skip
    • test.skipIf
    • test.runIf
    • test.only
    • test.concurrent
    • test.sequential
    • test.todo
    • test.fails
    • test.each
    • test.for
  • bench
    • bench.skip
    • bench.only
    • bench.todo
  • describe
    • describe.skip
    • describe.skipIf
    • describe.runIf
    • describe.only
    • describe.concurrent
    • describe.sequential
    • describe.shuffle
    • describe.todo
    • describe.each
  • Setup and Teardown
    • beforeEach
    • afterEach
    • beforeAll
    • afterAll
  • Test Hooks
    • onTestFinished
    • onTestFailed

@vue/test-utils

docs: @vue/test-utils

  • mount
    • attachTo
    • attrs
    • data
    • props
    • slots
    • global
    • shallow
  • Wrapper methods
    • attributes
    • classes
    • emitted
    • exists
    • find
    • findAll
    • findComponent
    • findAllComponents
    • get
    • getComponent
    • html
    • isVisible
    • props
    • setData
    • setProps
    • setValue
    • text
    • trigger
    • unmount
  • Wrapper properties
    • vm
  • shallowMount
  • enableAutoUnmount
  • flushPromises
  • config
    • config.global
    • components
    • RouterLinkStub

@nuxt/test-utils

docs: @nuxt/test-utils

  • $fetch(url)
  • fetch(url)
  • url(path)

helpers

- `mountSuspended`: wraps mount from [@vue/test-utils](#vue-test-utils), so you can check out the Vue Test Utils documentation for more on the options you can pass, and how to use this utility.

@testing-library/jest-dom

docs: @testing-library/jest-dom

  • ByRole
  • ByLabelText
  • ByPlaceholderText
  • ByText
  • ByDisplayValue
  • ByAltText
  • ByTitle
  • ByTestId
  • User Actions
    • Firing Events
    • Async Methods
    • Appearance and Disappearance
    • Considerations for fireEvent
    • Using Fake Timers
  • Advanced
    • Accessibility
    • Custom Queries
    • Debugging
    • Querying Within Elements
    • Configuration Options

@testing-library/vue

How to know what documentation to look up / research to understand your setup

Start with the Core Context: Understand Your Project’s Architecture

  1. Identify Your Main Components: In this case, you’re working with Nuxt 3, which is Vue-based, so you know you’ll need tools that cater to Vue/Nuxt-specific needs.
  2. Analyze the Testing Stack: Since you’re using Nuxt with Vitest and @nuxt/test-utils, it’s helpful to understand what each tool is meant to do:
    • Nuxt 3: The framework that structures your application.
    • Vitest: The test runner, responsible for running tests.
    • @nuxt/test-utils: A layer for Nuxt-specific testing utilities to simplify testing with Nuxt's SSR and component features.

Follow a Layered Documentation Path (Dependency Graph):

In a structured setup, there’s an implicit “dependency graph” based on which tool depends on the other:

  1. Start with the main framework (Nuxt 3).
  2. Move to framework-specific utilities (@nuxt/test-utils) for any special setup.
  3. Check the testing framework (Vitest) for configurations like setup files, global imports, etc.
  4. Finally, go to add-on libraries (@testing-library/jest-dom and @testing-library/vue) that enhance functionality for specific tasks (e.g., custom matchers, DOM querying).

How to systematically approach config/setup to avoid extra configurations and unnecessary setups:

1. Check Each Layer for Defaults and Opinionated Configurations

Start by examining the highest-level library in your stack, which in this case is @nuxt/test-utils. Often, the documentation for these higher-level utilities will state what it includes or modifies by default. In the case of @nuxt/test-utils, it may already:

  • Wrap @vue/test-utils methods like mount and render.
  • Configure SSR and Nuxt-specific features that Vue Test Utils alone wouldn’t handle.
  • Handle imports and setup for Vue-related configurations that you would otherwise need to configure manually.

Tip: Look for documentation or guides on default configurations or opinionated setups in higher-level libraries. For example, @nuxt/test-utils might already configure things like component mounting in a way that works seamlessly with Nuxt’s SSR and file structure.

2. Use a Minimal Setup First, Then Incrementally Add Only What’s Missing

Instead of fully configuring each layer from the ground up:

  • Start with just @nuxt/test-utils and Vitest, and attempt to run a simple test.
  • Check for missing features or errors that arise. Only add configurations when something isn’t working as expected.

For example:

  • If a matcher like toBeInTheDocument throws an error, you’ll know you need to add @testing-library/jest-dom.
  • If components aren’t rendering correctly, then check if you need to add @vue/test-utils or additional configurations.

This iterative process lets you use the defaults from each tool, only adding configurations when necessary.

3. Rely on Nuxt-Specific Documentation for Simplified Setups

Frameworks like Nuxt often provide guides for “recommended” setups, which handle the nuances of their specific environment. In the case of @nuxt/test-utils:

  • Nuxt’s testing documentation might not cover every detail but can point you to configurations that work well for Nuxt 3.
  • Community resources are also invaluable here. Look for setup examples or shared configurations on GitHub, Nuxt's forums, or Stack Overflow to see how others have configured similar stacks without extra boilerplate.

4. Watch for Configuration Collisions and Overlapping Features

When adding libraries at different levels, you might encounter overlapping configurations. For example:

  • Mounting Methods: Both @vue/test-utils and @nuxt/test-utils provide mount, but the @nuxt/test-utils version includes Nuxt-specific setups. Avoid importing mount from @vue/test-utils directly if you’re using @nuxt/test-utils, as it could lead to redundancy.
  • DOM Utilities: Libraries like @testing-library/vue wrap around @vue/test-utils but with added query functions. If you’re using @testing-library/vue, you may not need to rely on all of @vue/test-utils directly.

Rule of Thumb: Always prefer the higher-level library’s functions first, as they’re usually tailored for your stack. Only bring in lower-level utilities if you explicitly need something more granular.

5. Experiment with Removing Configurations (If Unsure)

Once your setup works, you can safely experiment by removing or commenting out configurations to see if they are truly necessary.

  • For instance, if you imported @vue/test-utils methods directly but find that @nuxt/test-utils handles everything, you may not need that direct import.
  • Similarly, if Vitest runs without an explicit setup file, you might not need to manually configure it.

This approach helps you keep configurations minimal while verifying that each setting is required.


Example Approach for Your Setup

  1. Start with Nuxt and Vitest Configurations Only:

    • Install @nuxt/test-utils and Vitest.
    • Create a minimal Vitest configuration (vitest.config.ts), just specifying globals if needed.
  2. Add Simple Tests and Identify Gaps:

    • Run basic tests with only @nuxt/test-utils. If component rendering works, you know it’s handling @vue/test-utils setups.
    • If you see a need for custom matchers (toBeInTheDocument), then add @testing-library/jest-dom in a setup file.
  3. Iterate As Needed:

    • Only add @testing-library/vue if you need advanced DOM querying that isn’t easily handled by @nuxt/test-utils alone.
    • Check the output of tests carefully. If everything runs smoothly, you’re set; if not, incrementally add only what’s necessary.

Recap

By starting at the highest level (@nuxt/test-utils), adding only what’s missing, and experimenting with minimal configurations, you can avoid redundant setups. This pragmatic approach gives you a working environment with the least configuration, leveraging each library’s defaults and higher-level abstractions.

In short:

  1. Trust higher-level libraries to handle lower-level details.
  2. Add configuration incrementally to address specific needs.
  3. Test and refine by removing what might be redundant.

How to know which methods/functions/etc. you can/should use from the layers of wrappers

When a higher-level library or utility says it “wraps” certain methods from a lower-level library, it generally means it provides its own versions of those methods, with added configurations, behavior, or defaults specific to the higher-level library's context. In practice, this has a few important implications for setup, configuration, and usage:

1. Simplifies Setup and Configuration

When a method is “wrapped” by a higher-level library, it often comes pre-configured with defaults tailored to the specific environment or framework (like Nuxt, in your case). This means:

  • You don’t need to set up or configure the underlying method explicitly.
  • Any complex setup steps that would be required if you used the lower-level library directly are often handled for you.

Example: @nuxt/test-utils might wrap mount from @vue/test-utils to automatically include SSR capabilities and load Nuxt-specific plugins and components. If you were using @vue/test-utils directly, you’d likely have to configure these things manually.

2. Use the Wrapped Methods Instead of the Original Ones

Pragmatically, this means you should:

  • Favor the wrapped method provided by the higher-level library rather than importing and using the method from the lower-level library directly.
  • Avoid redundant imports of lower-level methods because the higher-level versions are designed to fit better with your stack.

Example: If @nuxt/test-utils provides a mount method, you should use that mount instead of importing mount from @vue/test-utils. The Nuxt wrapper likely includes necessary configurations (like handling the Nuxt context), which @vue/test-utils on its own wouldn’t.

3. Leverages Added Convenience or Abstractions

Wrapped methods often come with additional functionality or convenience features that the lower-level method doesn’t provide. These could include:

  • Automated Context Setup: Setting up things like Nuxt’s context, middleware, and SSR automatically.
  • Enhanced Defaults: Pre-configured options for typical use cases, which can save you from needing to manually configure them.
  • Error Handling: Extra error handling or specific error messages relevant to the higher-level framework.

Example: When @nuxt/test-utils wraps mount, it might add support for Nuxt’s specific features like pages, layouts, and async data fetching, allowing you to test Nuxt components without needing to mock those features manually.

4. Consider Reduced Flexibility (When You Might Need the Original)

Sometimes, wrapped methods may abstract away certain options, making it harder to customize specific low-level behavior. In rare cases where you need fine-grained control:

  • You might need to bypass the wrapped method and go directly to the lower-level method.
  • Or, you might pass additional options to the wrapped method if it supports that.

Example: If you’re testing something highly specific to Vue without Nuxt-specific behavior, you might prefer using @vue/test-utils’s mount to avoid any extra configuration or behavior that @nuxt/test-utils injects.

5. Read Documentation of the Wrapping Library for Details on Added Behavior

The higher-level library’s documentation usually specifies what its wrapped methods include, so:

  • Review those descriptions to understand what additional features, configurations, or limitations the wrapper introduces.
  • Focus on the options that the wrapped methods support since these options might be enhanced or restricted compared to the original.

Recap

When you see “we wrap XYZ methods like A and B,” here’s a practical approach:

  • Use the wrapped method from the higher-level library by default to take advantage of pre-configured behavior and settings.
  • Check documentation for any additional features or limitations added by the wrapper.
  • Skip setting up or configuring the lower-level method explicitly unless you have a very specific reason.
  • Only use the original lower-level method directly if the wrapped method doesn’t meet your needs (e.g., if you need fine-grained control not offered by the wrapped method).

By relying on wrapped methods, you save time on setup and avoid unnecessary configurations, as the higher-level library is handling many details for you.

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