skip to content

With Vue Test Utils, how do you mount a component that calls `inject('theme')` and renders `$t('title')` without installing the real i18n plugin?

level: juniorimportance: must knowfreq 55%

answer

  1. the mount option named global
  2. two different lookup channels
  3. app provides vs instance properties
  4. provide for inject, mocks for $-globals

basics

~20 s

Pass both under mount's global option: global.provide with a theme key fills the app-level provides that inject('theme') reads, and global.mocks with a fake $t puts that property on the component instance so the template's $t call resolves.

solid answer

~40 s

The component reads from two different channels, so it needs two different fakes. `inject('theme')` looks up the app's provides, which `global.provide: { theme: 'dark' }` fills: Vue Test Utils calls `app.provide()` for every key before mounting. `$t` in the template is an **instance property** that the real i18n plugin would add, so `global.mocks: { $t: (key) => key }` fakes it: Vue Test Utils installs a global mixin that sets the mock on every component instance, including `<script setup>` ones. Leave either out and Vue tells you: a missing injection logs `injection "theme" not found.` and returns `undefined`, and the missing `$t` fails the render when the template calls it.

code

vue · 9 lines
vue
<script setup lang="ts">
import { inject } from 'vue'

const theme = inject<string>('theme')
</script>

<template>
  <h1 :class="`header--${theme}`">{{ $t('title') }}</h1>
</template>

go deeper

for a junior

Recall that mount takes a global option, that provide feeds inject and that mocks feeds $-prefixed plugin globals. Be able to write the two-line mount call.

for a middle

Explain why these are two channels: inject walks the provides chain ending at app.provide, while $t is a property read on the render proxy. Know what a missing injection does.

for a senior

Show judgment about when a fake hides a real bug, such as an inject default masking a forgotten provide, and when the real plugin with a tiny message set is the better test.

for a principal

Be ready to argue where suite-wide fakes live: a shared config.global default versus per-test options, and how that choice affects what each test proves.

## What the component actually depends on A component that themes itself with `inject('theme')` and translates with `$t('title')` looks self-contained, but it quietly depends on two things that are normally set up once in `main.ts`: - an **injection** called `theme`, which some ancestor or the app itself must `provide`; - a **global instance property** called `$t`, which an i18n plugin normally adds to every component. In production both arrive because the app was built with `createApp(App)`, then `app.provide(...)` and `app.use(i18n)`. In a test, `mount()` from `@vue/test-utils` builds a brand-new app around the component and knows nothing about your `main.ts`. Unless you tell it otherwise, neither dependency exists. The key insight is that these are **two different lookup channels**, and each has its own mounting option. ## Channel one: `inject` reads the provides chain In Vue 3, `inject(key)` walks up the component's provides chain. For the root component of an app, that chain ends at the **app-level provides** that `app.provide(key, value)` writes. Vue Test Utils exposes that directly: - `global.provide` is a plain object; for every own key (string or `Symbol`), Vue Test Utils calls `app.provide(key, value)` on the app it creates for this mount; - the component's `inject('theme')` then finds `'dark'` exactly as if `main.ts` had provided it; - if your code uses an `InjectionKey` symbol, you must import that same symbol and use it as a computed key, `{ [ThemeKey]: 'dark' }`, because keys match by identity. ## Channel two: `$t` is an instance property The template expression `$t('title')` is not an injection. It is a property read on the component's render proxy. A real i18n plugin makes it resolve by adding a global property during `app.use(...)`. To fake it, Vue Test Utils offers `global.mocks`: - `global.mocks` is an object of instance properties, documented as meant for values injected by third-party plugins, such as `$t`, `$store` or `$route`; - Vue Test Utils applies it through a mixin whose `beforeCreate` puts each mock on the instance; - for `<script setup>` components it also wraps the render proxy so a template read of `$t` returns the mock. The docs warn that `global.mocks` is **not** for Vue's own properties such as `$root`. ## Putting both together ```ts import { mount } from '@vue/test-utils' import ThemedHeader from './ThemedHeader.vue' const wrapper = mount(ThemedHeader, { global: { provide: { theme: 'dark' }, mocks: { $t: (key: string) => `t:${key}` } } }) ``` A mock that returns its key with a visible prefix is a common choice: the test can assert that the right translation key was used without depending on real message files. ## What goes wrong without each option | Missing option | What Vue does | What the test sees | |---|---|---| | `global.provide` | dev warning `injection "theme" not found.` and `inject` returns `undefined` | the component renders with an undefined theme, so a class or style is wrong | | `global.mocks` for `$t` | dev warning that `$t` was accessed during render but is not defined on instance | the call on `undefined` throws and the render fails | | both present | nothing unusual | the component renders as in the app | Note that a missing injection does **not** throw. If `inject` was given a default, such as `inject('theme', 'light')`, there is not even a warning; the default silently wins, which can hide a test that forgot its provide. ## When a fake is the wrong tool Faking is the right call when the test is about **this component's** behaviour: which key it asks to translate, which class it applies for a theme. Two cases argue for the real thing instead: 1. The component calls a composable such as `useI18n()` rather than the `$t` global. Composables usually read an injection, which `global.mocks` never reaches; you then need `global.provide` with the library's key, or the real plugin in `global.plugins`. 2. The test is really about integration, such as a pluralisation rule or a locale switch. Then install the real plugin with a small message set through `global.plugins`. If every test in the suite needs the same `$t` fake, move it to `config.global.mocks` in a setup file rather than repeating it in each `mount`. ## Choosing what the fakes return A fake should make the assertion meaningful, not merely stop the warnings: - a `$t` that returns the key, or the key with a prefix, lets the test check **which** message the component asked for; - a `$t` that returns an empty string hides missing keys and makes text assertions useless; - a provided theme should be a value the component visibly reacts to, such as a class name, so the test proves the injection was read; - if the component should react to a theme change after mounting, provide a `ref` instead of a plain string and have the component read it reactively.

  • The component injects with an exported `ThemeKey` symbol instead of the string 'theme'. Does `global.provide: { theme: 'dark' }` still work?
    No. Injection keys match by identity, so a string key never satisfies a symbol lookup. Import the same exported `ThemeKey` and use a computed key: `global.provide: { [ThemeKey]: 'dark' }`. Vue Test Utils iterates the object's own keys, symbols included, and calls `app.provide` for each, so the symbol reaches the component.
  • Could you set `$t` through `global.config.globalProperties` instead of `global.mocks`?
    Yes. Vue Test Utils merges `global.config` into the new app's `app.config`, so a `globalProperties.$t` becomes visible on every component, which is how the real plugin does it. `global.mocks` is the documented tool for faking plugin-supplied properties and also patches `<script setup>` instances directly; either works for a template `$t`.

It is like staging a play without the real building: global.provide sets the props on stage that actors pick up (inject), while global.mocks hands an actor the line cards (instance properties) the real producer would have given them.

saying these in an interview costs you the question

  • global.provide also makes $t available, because both are app-level globals.
  • Passing theme as a prop satisfies inject, since inject falls back to props.
  • A missing injection makes mount throw, so a forgotten provide always fails loudly.
  • global.mocks is the way to override Vue's own properties such as $root or $el.
  • Any component that uses $t forces you to install the real i18n plugin.