vuejs / vue-test-utils

Component Test Utils for Vue 2
https://vue-test-utils.vuejs.org
MIT License
3.57k stars 669 forks source link

Errors in `setup()` are silent if `render()` throws an error #2083

Open mikemonteith opened 10 months ago

mikemonteith commented 10 months ago

Subject of the issue

Errors thrown in setup() are silently caught if the render function throws it's own error. This can lead to confusing test error messages that seem like something is wrong in the template, when the issue is actually in the setup.

Consider the reproduction case below:

Steps to reproduce

package.json:

{
  "name": "vue-test-utils-bug",
  "version": "1.0.0",
  "description": "",
  "main": "test.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@vue/test-utils": "^2.4.1"
  },
  "devDependencies": {
    "global-jsdom": "9.1.0",
    "jsdom": "22.1.0"
  }
}

test.js:

import 'global-jsdom/register'
import { mount } from '@vue/test-utils'

// The component to test
const MessageComponent = {
  template: `
    <p>{{ t('key') }}</p>
  `,
  setup: () => {
    throw Error("test error")
    return {
      t: (key) => "A translation"
    }
  },
}

// mount() returns a wrapped Vue component we can interact with
const wrapper = mount(MessageComponent)

Expected behaviour

I would expect the program to stop when setup() throws the error.

Actual behaviour

The program continues by rendering the template with an empty context, which then throws it's own error due to t being undefined.

Output:

[Vue warn]: Property "t" was accessed during render but is not defined on instance. 
  at <Anonymous ref="VTU_COMPONENT" > 
  at <VTUROOT>
undefined:9
    return (_openBlock(), _createElementBlock("p", null, _toDisplayString(t('key')), 1 /* TEXT */))
                                                                          ^

TypeError: t is not a function
    at Proxy.render (eval at compileToFunction (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/vue/dist/vue.cjs.js:66:18), <anonymous>:9:75)
    at renderComponentRoot (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:817:16)
    at ReactiveEffect.componentUpdateFn [as fn] (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5705:46)
    at ReactiveEffect.run (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/reactivity/dist/reactivity.cjs.js:182:19)
    at instance.update (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5818:51)
    at setupRenderEffect (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5826:5)
    at mountComponent (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5616:5)
    at processComponent (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5569:9)
    at patch (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5055:11)
    at ReactiveEffect.componentUpdateFn [as fn] (/Users/mike/dev/MIMO16/vue-jest-bug/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5712:11)
mikemonteith commented 10 months ago

Is this a vue thing?

I've just found that this gives me the behaviour I want:

const wrapper = mount(MessageComponent, {
  global: {
    config: {
      errorHandler: (err) => {
        throw err;
      },
    }
  },
});
mikemonteith commented 10 months ago

I've ended up adding this to our setup.js file to set this behaviour in all tests.

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

// By default, vue will attempt to continue past setup errors.
// This will cause tests to fail silently, so we make sure that
// exceptions are thrown immediately in all tests.
config.global.config.errorHandler = (err) => {
  throw err;
};

Maybe this could be more prominent in the docs?

Bastczuak commented 6 months ago

@mikemonteith dude you saved my day. I was chasing false errors.

TypeError: Cannot read properties of undefined (reading 'map')
 ❯ src/views/SubscriptionSelectSkeleton.vue:12:33
     10|       <v-card-text>
     11|         <v-select
     12|           :items="subscriptions.map(({ fqdn, id, stackName }) =>
       |                                 ^

In my case I was accessing an undefined value in useRoute in <script setup>, which let to this error. subscriptions was always a valid array.

Adding your code gave me the correct error output.

TypeError: Cannot read properties of undefined (reading '0')
 ❯ setup src/views/SubscriptionSelectSkeleton.vue:53:27
     51| const route = useRoute()
     52| const router = useRouter()
     53| const path = route.matched[0].path
                                   ^

In hindsight it makes sense, because the actual function which defines subscriptions was then never called.

joaopedrodcf commented 5 months ago

@mikemonteith I think from the version of your package.json it seems you opened the issue on the wrong repository:

 "@vue/test-utils": "^2.4.1"

This repository is for version 1 for the version 2: https://github.com/vuejs/test-utils/issues and the version you currently have: https://github.com/vuejs/test-utils/releases/tag/v2.4.1

mikemonteith commented 5 months ago

@joaopedrodcf Well spotted. In that case, my issue is a duplicate of https://github.com/vuejs/test-utils/issues/2319

joaopedrodcf commented 5 months ago

No problem glad to help ✌️