skip to content

With Vue Test Utils, how do `config.global` defaults combine with a test's own `global` mounting options, and where does that merge surprise people?

level: middleimportance: should knowfreq 40%

answer

  1. suite-wide defaults in a setup file
  2. arrays append, objects merge by key
  3. a per-test list adds, never replaces
  4. one shared object across tests

basics

~20 s

Every mount merges config.global into its own global option: plugins and mixins are appended, config entries first; provide, mocks, components, directives and stubs merge by key with the test's value winning. config.global is shared, so a change inside one test persists.

solid answer

~40 s

`config.global`, imported from `@vue/test-utils`, holds defaults that apply to every `mount`; it is usually filled in a test setup file with things like a `$t` mock, global components and directives. On each mount Vue Test Utils merges it with the test's `global`: **arrays concatenate** (`plugins`, `mixins`, config's entries first), **objects merge one level by key** (`provide`, `mocks`, `components`, `directives`, `stubs`), and the test's value wins on a clash. Three surprises follow. Passing `plugins: [router]` in a test **adds** to the default plugins rather than replacing them. Overriding one `provide` key keeps all the other default keys. And because `config` is one shared object, assigning to `config.global.mocks` inside a test leaks into later tests until you restore it.

code

ts · 13 lines
ts
import { config, mount } from '@vue/test-utils'
import ThemedHeader from './ThemedHeader.vue'

// setup file defaults
config.global.provide = { theme: 'light', locale: 'en' }
config.global.mocks = { $t: (key: string) => key }

test('a per-test provide overrides one key and keeps the rest', () => {
  const wrapper = mount(ThemedHeader, {
    global: { provide: { theme: 'dark' } } // locale 'en' still provided
  })
  expect(wrapper.classes()).toContain('header--dark')
})

go deeper

for a junior

Recall that config.global sets defaults for every mount and that a test's own global option can override them.

for a middle

Explain the merge: arrays append with defaults first, object options merge per key with the test winning, and the defaults are one shared object.

for a senior

Spot order-dependent failures caused by mutating config.global inside tests, and keep stateful collaborators out of the shared defaults.

for a principal

Decide what the suite's defaults should contain so that tests stay readable without hiding the dependencies each test really has.

## What `config.global` is for `@vue/test-utils` exports a `config` object. Its `global` field has the same shape as the `global` mounting option: `plugins`, `config`, `mixins`, `mocks`, `provide`, `components`, `directives`, `stubs` and `renderStubDefaultSlot`. Whatever you put there is used **by default every time you call `mount`**. The typical home is a test setup file that runs before the suite: ```ts import { config } from '@vue/test-utils' import { vFocus } from '@/directives/focus' import BaseIcon from '@/components/BaseIcon.vue' config.global.mocks = { $t: (key: string) => key } config.global.components = { BaseIcon } config.global.directives = { focus: vFocus } ``` This mirrors what `main.ts` does with `app.component(...)`, `app.directive(...)` and `app.use(...)`: anything registered globally in the real app must also be registered for tests, or Vue warns that it failed to resolve the component or directive. ## How the merge works For each mount, Vue Test Utils builds one merged `global` from the defaults and the test's own options. The rules differ by field type: | Field | Merge rule | Winner on a clash | |---|---|---| | `plugins`, `mixins` | arrays concatenated, defaults first | both are applied | | `provide`, `mocks`, `components`, `directives` | shallow object spread | the test's key | | `stubs` | merged key by key; the array form means `true` for each name | the test's key | | `config` | spread one level, with `globalProperties` merged separately | the test's key | | `renderStubDefaultSlot` | the test's value if set, else the default | the test's value | The merged result is then applied to the fresh app that `mount` creates: the mocks mixin, app config, then `provide`, then `plugins` through `app.use`, then mixins, then `app.component` for each component that is not also stubbed, then `app.directive`. ## Surprise 1: a per-test plugin list adds, it does not replace If the setup file sets `config.global.plugins = [i18n]` and a test passes `global: { plugins: [router] }`, both are installed, `i18n` first. There is no way to *remove* a default plugin with the mount option alone. If one test needs the plugin absent, that test must temporarily change `config.global.plugins` and restore it afterwards. Listing the same plugin object in both places installs it once: Vue's `app.use` remembers installed plugins per app and, in development, warns `Plugin has already been applied to target app.` for the second attempt. ## Surprise 2: object options merge per key With `config.global.provide = { theme: 'light', locale: 'en' }` and a test passing `provide: { theme: 'dark' }`, the component sees `theme: 'dark'` **and** `locale: 'en'`. That is usually what you want, but it means a test cannot make a key disappear by leaving it out. To test the missing-injection path, the default must not contain that key. ## Surprise 3: the config object is shared `config` is created once and stored on the global object under a versioned symbol, so every mount in that test environment reads the same object. Consequences: - assigning `config.global.mocks.$t = ...` inside one test changes every later test in that environment; - the Vue Test Utils docs say this outright: the behaviour is global, so you may need to enable it before and disable it after a test; - a clean pattern is to save the old value in `beforeEach`, change it, and restore it in `afterEach`, or better, pass the override through the mount's own `global` option, which is scoped to that one mount. ## A checklist for a clean setup file 1. Register everything `main.ts` registers globally that is **stateless**: components, directives, a `$t` mock. 2. Keep defaults minimal, so each test still shows the collaborators it relies on. 3. Never assign to `config.global` inside a test body without restoring it. 4. Prefer the mount's own `global` option for anything only one test needs. ## What belongs in the defaults Put **stateless** things in `config.global`: a `$t` mock that returns keys, globally registered presentational components, directives, and stubs that every test wants. Keep **stateful** collaborators, such as a router or a store instance, out of it and create them per test, because the defaults are shared while each mount's app is not.

  • A component uses a globally registered `<BaseIcon>` and a `v-focus` directive from `main.ts`. What happens in a test that registers neither?
    Vue warns that it failed to resolve the component or directive, renders the tag as a plain unknown element and skips the directive. Register them in `config.global.components` and `config.global.directives` once, or per test in `global.components` and `global.directives`; Vue Test Utils passes them to `app.component` and `app.directive` on each fresh app.
  • Where should a test that needs one default plugin absent make that change?
    The mount option cannot remove it, because plugin arrays are concatenated. Change `config.global.plugins` for that test only, saving the original in `beforeEach` and restoring it in `afterEach`, or move that plugin out of the defaults and have the tests that need it pass it explicitly.

saying these in an interview costs you the question

  • Passing global.plugins in a test replaces the plugins from config.global.
  • Leaving a key out of a test's provide removes that key's default.
  • config.global is copied per test file, so changing it inside a test is harmless.
  • Globally registered components from main.ts are available in tests automatically.
  • A router instance belongs in config.global so every test shares one.