The concept of testing Vue/Js is new to me,so bear with me. Normally if there are errors related to the Vue component I can see them in the browser console.
for example if I have a Vue component:
<template>
<div>
{{ number }}
</div>
</template>
<script>
export default {}
</script>
I will see an error like this:
[Vue warn]: Property or method "number" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Now when I want to test a specific component using test tools, how can I check for these kind of errors, I want to make sure that the component is loaded without these errors.
Today I tried using Jset to test this, and I ended up with this:
import { mount } from '#vue/test-utils'
import Component from '../Components/Example.vue'
test('it renders without errors', () => {
const wrapper = mount(Component)
expect(wrapper.isVueInstance()).toBe(true);
})
but, that doesn't do it since it will always return true even though 'number' is not defined. How can I check if the component has loaded without console errors.
This is not an error but warning, this is the reason why the component renders.
A proper testing strategy is to test the behaviour because that the framework doesn't cause warnings doesn't mean that a unit works correctly. A suite with full code coverage won't cause such warnings without a fail.
console.error calls can be additionally spied to strengthen the test but in this case this is entirely redundant:
spyOn(console, 'error');
const wrapper = mount(Component);
expect(wrapper.find('div').text()).toContain('123456'); // easier to do with a snapshot
expect(console.error).not.toHaveBeenCalled();
Notice that console and some other built-in methods should be spied and mocked with care because they interfere with testing framework's error reporting.
Related
I'm testing my Vue components for the first time using Mocha and Webpack and set everything up according to the docs.
However, in many of my components, I use a global event bus to communicate and emit events between components. For example, the following snippet is in the created hook of one of my single file components.
created() {
Event.$on("activate-edit-modal", (listing) => {
this.isModalActive = true; // show delete modal
this.listing = listing; // set listing as the emitted listing
});
},
Unfortunately, when I run the following test code in my test file (npm run test), I get the following error.
import { mount } from '#vue/test-utils';
import editModal from '../../../src/components/admin/editModal.vue';
const wrapper = mount(editModal);
console.log(wrapper);
Error Msg: I'm aware the error msg is referring to the created hook (in the code snippet above) and highlighting that "Event.$on" in that created hook is not a function.
WEBPACK Compiled successfully in 2331ms
MOCHA Testing...
[Vue warn]: Error in config.errorHandler: "TypeError: Event.$on is not
a function" TypeError: Event.$on is not a function
at VueComponent.created ...
How should I test my components that use a global event bus? Note that I am not interested in testing the event bus itself; however, I'm unaware on how I can proceed to test other aspects of the component with this error.
The global event bus "Event" that I use in all my components is declared in /src/main.js as shown below
import Vue from 'vue';
import App from './App.vue';
import router from "./router";
import store from "./store";
window.Event = new Vue();
let app = new Vue({
el: '#app',
router,
store,
render: h => h(App)
});
You're trying to reference a local event bus called Event. You should call the bus you registered on the window object, like this: window.Event.$on("activate-edit-modal"....
After you've ensured that your component is calling the bus registered on the window object as shown above, make sure you also add the following before you mount your component in the test file like so:
import Vue from 'vue';
window.Event = new Vue();
const wrapper = mount(adminResults);
Your global event bus "Event": where is it defined? I can't see it being imported anywhere into the component with the error. I suspect this is your problem.
Beware global event bus is a top five antipattern, according to one of the presentations at the recent vue conf. I much prefer a plain global javascript object as a global state store.
You can mock your event bus and assert that methods are called on it with correct parameters.
For instance, in the above scenario try window.Event = { $on: sinon.spy() }.
After mounting you should be able to assert that $on was called with correct parameters.
Here's documentation on Mocha and spies. https://github.com/mochajs/mocha/wiki/Spies
I'm not as familiar with mocha so I'm not exactly sure I've got the details correct.
I have a dynamic component similar to:
{{component fooProperty owner=this}}
fooProperty is data driven and sometimes comes off incorrectly, at least now in dev, but I'm afraid it may come off incorrectly in prod too (due to app versioning, persisted storage etc). Basically, I don't trust this to be always correct (ie. resolvable to a component). When the value is off, the entire app crashes though:
Uncaught Error: Assertion Failed: HTMLBars error: Could not find component named "some-inexisting-component" (no component or template with that name was found)
EmberError # ember.debug.js:19700
assert # ember.debug.js:6719
assert # ember.debug.js:19502
componentHook # ember.debug.js:10894
render # ember.debug.js:12782
render # ember.debug.js:12732
handleKeyword # ember.debug.js:46584
keyword # ember.debug.js:46709
exports.default # ember.debug.js:12483
handleKeyword # ember.debug.js:46545
handleRedirect # ember.debug.js:46531
...
I would prefer to catch such exception and prevent the entire app from crashing. I can think of a workaround eg. vetting the fooProperty return against App.__container__.lookup and returning a generic 'missing' component. But I would still prefer if there is a way to capture and handle exception as raised during rendering, if possible.
The simple answer is "No, but you can implement your own." Template engines have never had a role of exception handling. It's relevant not only for Handlebars/HTMLbars case but other template engines like JSX, Jinja, etc. as exception handling has quite a bit logic involved that has to be specified on the user side and it just not seems in the scope of presentation role they have. The main reason for that appears to be a separation of concerns which Ember Framework developers want to enforce to avoid developers ending up with a lot of additional spaghetti code in their apps which can substantially decrease overall code readability and maintainability.
A possible solution for your case:
component.js
import Component from 'ember-component';
import get from 'ember-metal/get';
import getOwner from 'ember-owner/get';
import { isEmpty } from 'ember-utils';
import computed from 'ember-computed';
export default Component.extend({
wantToRenderComponentName: 'name-that-doesnt-exist',
wantToRenderComponentNameExist: computed('wantToRenderComponentName', {
get() {
const owner = getOwner(this);
return !isEmpty(owner.lookup(`component:${get(this, 'wantToRenderComponentName')}`))
}
})
});
template.hbs
{{#if wantToRenderComponentNameExist}}
{{component wantToRenderComponentName}}
{{else}}
// handle your exception presentationally here
{{/if}}
The owner.lookup('component:component-name') would return a component instance if it is initialized within the app or undefined if it is not. I can imagine some cases where you would want to initialize some of the components at runtime if some condition is met to save memory so this is the way of checking if the component is initialized within the app and can be used in a template. It's not an exception handling as there are no exceptions raised in this code but it uses dynamic component naming, and that's why we can move the actual check for existence of the component to run-time in distinction with compile-time when you statically specify the name of the component to be rendered.
Assertions error will not be raised in production build. so your app wont crash. if component exist it will render otherwise it will silently fail.
I've set up an error substate route/controller/template according to http://guides.emberjs.com/v2.2.0/routing/loading-and-error-substates/. Manually browsing my app, I can trigger error conditions and get directed to the substate. Confirmed with Ember Inspector.
I'd like to automatically test the substate. However, Ember CLI's test runner fails any test when a route's model hook rejects. In other words, the test fails before I can navigate to the error substate.
How can I automatically test my error substate?
Ember: 2.2.0
Ember CLI: 1.13.13
Unfortunately it doesn't seem to be easy to do this in a clean manner.
In its internal tests, Ember uses bootApplication to the route which errors (see github) and is able to directly catch the error. Unfortunately if you try and do any form of try/catch or then/catch around a call to visit in your tests you will find it fails.
When you visit a link which results in an error substate from your acceptance test then Ember's defaultActionHandlers.error gets fired. By design it is not meant to be overridable. It calls logError which calls Ember.default.Logger.error.
So to test this substate we need to temporarily overwrite that method. We can also peek inside the ember container to access the currentRouteName like so (using sinon for the spying):
test('when there is an API error an error message is shown', function(assert) {
const emberLoggerError = Ember.Logger.error;
Ember.Logger.error = sinon.spy();
visit('/users/');
andThen(() => {
// This could be nicer and less private with `getOwner`
let { currentRouteName } = this.application.__container__.lookup('router:main');
assert.equal(currentRouteName, 'users.index_error', 'The current route name is correct');
assert.equal(Ember.Logger.error.callCount, 1, 'The error logger was called');
// Restore the Ember.Logger
Ember.Logger.error = emberLoggerError;
});
});
Things can get even more complicated though. If your visit happens inside a Promise (it did in our case because we were using ember-page-object for our tests) then you have more to deal with...
In a separate loop onerrorDefault of RSVP is triggered which calls Test.adapter.exception AND Ember.default.Logger.error (again!) - passing the stack. So in this case you need to stub and spy on Test.adapter.exception and expect Ember.default.Logger.error to have been called twice!
I am using Ember with formatjs to internationalize my application, and ember-cli to build it all.
When I generate a component with
ember g component some-component
Ember also creates a test that checks that the component renders. However, if I use the intl-get helper from formatjs in the component template, the unit test fails.
So how can I register the custom helpers that formatjs creates for a unit test?
I first tried to add the intl-get helper:
moduleForComponent('some-component', {
needs: ['helper:intl-get']
});
However, this just fails inside intl-get when it tries to access "intl:main". I would like for the intl initializer to run, but I am not sure if there is even application setup. Or is it some way to just mock those methods using sinon?
My current workaround is to just delete the 'it renders' tests. But I would like for these tests to pass as well, so I can further test rendering later if I want.
Try:
moduleForComponent('some-component', {
integration: true
});
TLDR
I'm trying to unit test a very simple component. However, it appears some very common test helpers aren't being defined. This is something specific about unit-testing a component, as I'm using these in integration tests without issue.
Now just jump straight to the Questions at the end.
Details
The errors are generic:
click is not defined
andThen is not defined
Stack trace for context:
Died on test #4 at Object.test (http://localhost:7357/assets/test-support.js:110:11)
at http://localhost:7357/assets/skylab.js:14977:15
at mod.state (http://localhost:7357/assets/vendor.js:150:29)
at tryFinally (http://localhost:7357/assets/vendor.js:30:14)
at requireModule (http://localhost:7357/assets/vendor.js:148:5)
at Object.TestLoader.require (http://localhost:7357/assets/test-loader.js:29:9)
at Object.TestLoader.loadModules (http://localhost:7357/assets/test-loader.js:21:18): click is not defined
The component and the tests are very basic. The component:
import Ember from 'ember';
export default Ember.TextField.extend({
classNames: ['input-span']
});
The Test:
import Ember from 'ember';
import {
moduleForComponent,
test
} from 'ember-qunit';
moduleForComponent('custom-input');
test('focus on click', function(assert) {
assert.expect(1);
var component = this.subject();
this.render();
click('input')
assert.ok(component.$('input').is(':focus'));
});
Best Guess
My best guess is that these helpers work in the acceptance tests because the startApp helper creates the click and andThen helper functions. I don't have setup & teardown code in my moduleForComponent call, but it doesn't look like I should need it. And I don't want to test the whole app here -- just an isolated component.
Questions
Is there another way to inject these test helpers that I'm unaware of?
Am I writing these tests wrong? Should I never use click in a component test? Is the documentation simply outdated?
Should this be supported as-written, and is this a framework bug I should report?
acceptance level helpers currently depend on an app being spun up, as such they are not available for unit level tests. As those do not have an app.