2

I can't seem to get the following example to work with vue3 and testing library. https://github.com/testing-library/vue-testing-library/blob/main/src/tests/translations-vue-i18n.js

I've even tried to modify the example like so to get $t to be recognized by injecting messages into a mock but no luck.

Does anyone have an example that works with vue 3?

Here are the details ...

Translations.spec.js

import '@testing-library/jest-dom'
import {render, fireEvent} from '@testing-library/vue'
import Vuei18n from 'vue-i18n'
import Translations from '@/components/Translations'

const messages = {
  en: {
    Hello: 'Hello!',
    message: {
        hello: 'Hello!'
    }
  },
  ja: {
    Hello: 'こんにちは',
    message: {
        hello: 'こんにちは'
    }
  },
}

test('renders translations', async () => {
  const {queryByText, getByText} = render(Translations, {
    global: {
      mocks: {
        $t: (messages) => messages
      }
    }
  }, vue => {
    // Let's register and configure Vuei18n normally
    vue.use(Vuei18n)

    const i18n = new Vuei18n({
      locale: 'ja',
      fallbackLocale: 'ja',
      messages,
    })

    // Notice how we return an object from the callback function. It will be
    // merged as an additional option on the created Vue instance.
    return {
      i18n,
    }
  })

  //expect(getByText('Hello!')).toBeInTheDocument()

  //await fireEvent.click(getByText('Japanese'))

  expect(getByText('こんにちは')).toBeInTheDocument()

  //expect(queryByText('Hello!')).not.toBeInTheDocument()
})

Translations.vue

<template>
  <div>
    <h2>{{ $t("Hello") }}</h2>
    <h2>{{ $t("message.hello") }}</h2>
    <button @click="switchLocale('en')">English</button>
    <button @click="switchLocale('ja')">Japanese</button>
  </div>
</template>

<script>
export default {
  name: 'Translations',

  methods: {
    switchLocale(locale) {
      this.$i18n.locale = locale
    },
  },
}
</script>

package.json

{
  "name": "mc",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test:unit": "vue-cli-service test:unit"
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^1.2.35",
    "@fortawesome/free-solid-svg-icons": "^5.15.3",
    "@fortawesome/vue-fontawesome": "^3.0.0-4",
    "@popperjs/core": "^2.9.2",
    "bootstrap": "^5.0.2",
    "core-js": "^3.6.5",
    "es6-promise": "^4.2.8",
    "vue": "^3.1.4",
    "vue-hotjar": "^1.4.0",
    "vue-i18n": "^9.1.6",
    "vue-loader": "^16.2.0",
    "vue-router": "^4.0.10",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.14.8",
    "@babel/preset-env": "^7.14.8",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/vue": "^6.4.2",
    "@vue/cli-plugin-babel": "^4.5.13",
    "@vue/cli-plugin-eslint": "^4.5.13",
    "@vue/cli-plugin-router": "^4.5.13",
    "@vue/cli-plugin-unit-jest": "^4.5.13",
    "@vue/cli-plugin-vuex": "^4.5.13",
    "@vue/cli-service": "^4.5.13",
    "@vue/compiler-sfc": "^3.1.4",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/test-utils": "^2.0.0-rc.9",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-vue": "^7.0.0",
    "flush-promises": "^1.0.2",
    "prettier": "^2.3.2",
    "typescript": "^4.3.5",
    "vue-jest": "^5.0.0-alpha.10"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

Error

FAIL  tests/unit/Translations.spec.js        
  ● renders translations

    TestingLibraryElementError: Unable to find an element with the text: こんにちは. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div>
          <h2>
            Hello
          </h2>
          <h2>
            message.hello
          </h2>
          <button>
            English
          </button>
          <button>
            Japanese
          </button>
        </div>
      </div>
    </body>

      47 |   //await fireEvent.click(getByText('Japanese'))
      48 |
    > 49 |   expect(getByText('こんにちは')).toBeInTheDocument()
         |          ^
      50 |
      51 |   //expect(queryByText('Hello!')).not.toBeInTheDocument()
      52 | })

1
  • Translations work perfectly when the code is running. It's just the unit tests that don't seem to load the labels. I'm new to vie so I suspect this is a simple one Commented Aug 2, 2021 at 12:45

2 Answers 2

2

I had the same problem and solved it like this:

I am using the next version of @vue/test-utils and vue-jest ("@vue/test-utils": "^2.0.0-rc.16" + "vue-jest": "^5.0.0-alpha.10").

I created a file called jest.init.js (u can call it anything u like)

import { config } from '@vue/test-utils';
import translations from '@/locales/en';


config.global.mocks = {
  $t: (msg) => translations[msg],
};

and then initiate it as setup file in jest.config.js

module.exports = {
...
  setupFiles: [
    './tests/unit/jest.init.js',
  ],
...
};
Sign up to request clarification or add additional context in comments.

Comments

0

This answer is for everyone stumbling across that question when using Composition API where there's no global $t to mock.

I've solved it by exporting a function createConfiguredI18n in src/plugins/i18n.ts:

import { createI18n, I18nOptions } from 'vue-i18n'

import deDE from '@/locales/de-DE.json'
import enUS from '@/locales/en-US.json'

// Type-define 'de-DE' as the master schema for the resource
type MessageSchema = typeof deDE

export function createConfiguredI18n(locale: string, fallbackLocale: string) {
  return createI18n<I18nOptions, [MessageSchema], 'de-DE' | 'en-US'>({
    locale: locale || 'en-US',
    fallbackLocale: fallbackLocale || 'en-US',
    messages: {
      'de-DE': deDE,
      'en-US': enUS,
    },
  })
}

export const i18n = createConfiguredI18n('de-DE', 'en-US')

Then in the unit test you can do the following to initialize vue-i18n with your translations:

import {flushPromises, mount, VueWrapper} from '@vue/test-utils'
import {nextTick} from 'vue'
import {createConfiguredI18n} from '@/plugins/i18n'
...

describe('SubjectUnderTest', () => {
  it('should display translation "FooBar"', async () => {
    const locale = 'de-DE'
    const fallbackLocale = 'en-US'
    const wrapper = await createWrapper({locale, fallbackLocale})
    ...
  }

  async function createWrapper(options: {
    locale: string
    fallbackLocale: string
  }): Promise<VueWrapper> {
    const i18n = createConfiguredI18n(options.locale, options.fallbackLocale)

    const wrapper = mount(sut, {
      global: {
        plugins: [i18n],
      },
    })

    await nextTick()
    await flushPromises()

    return wrapper
  }
}

If you don't want the translations but instead mock them and check for the keys only, you can do the following in your unit test instead:

import {flushPromises, mount, VueWrapper} from '@vue/test-utils'
import {nextTick} from 'vue'
import {i18n} from '@/plugins/i18n'
...

i18n.global.t = (key) => key

describe('SubjectUnderTest', () => {
  it('should display translation for key "foo.bar"', async () => {
    const wrapper = await createWrapper()
    ...
  }

  async function createWrapper(): Promise<VueWrapper> {
    const wrapper = mount(sut, {
      global: {
        plugins: [i18n],
      },
    })

    await nextTick()
    await flushPromises()

    return wrapper
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.