I have a v-if rendering in my component that is link to a ref in the setup method.
How can I test the rendrering in my testing script.
Example:
In the component:
<div v-if="isCovid" class="covid"/>
setup() {
const isCovid = ref(true);
}
In the test component (I try this but dont work, the test received true):
it('do not render covid waring when its false', () => {
const wrapper = mount(TicketHeader,{
data(){
return { isCovid: false}
}
});
expect(wrapper.find('.covid').exists()).toBe(false)
});
If you are using the setup() method this is what counts. In the test you then use data() which AFAIK doesn't work if you use setup().
One small side note there, do you return isCovid so it is accessible in the
template?
I don't think you can modify a ref that you create in setup(). You'll probably need to provide something as a prop which you then access there. I just found this with an example:
https://lmiller1990.github.io/vue-testing-handbook/composition-api.html#the-component
We are using the aurelia component testing as defined here (with jest): https://aurelia.io/docs/testing/components#testing-a-custom-element
The component we are testing has a transient dependency. We are creating a mock for this dependency but when we run the tests using au jest, the real one always gets injected by the DI container and never the mock.
Here is the Transient service:
import { transient } from "aurelia-framework";
#transient()
export class ItemService {
constructor() {
}
getItems(): void {
console.log('real item service');
}
}
Here is the 'Mock' service (we have also tried using jest mocks but we get the same result):
import { transient } from "aurelia-dependency-injection";
#transient()
export class MockItemService{
getItems():void {
console.log('mock item service');
}
}
Here is the component under test:
import {ItemService} from "../services/item-service";
import { autoinject } from "aurelia-dependency-injection";
#autoinject()
export class TestElement {
constructor(private _itemService: ItemService) {
}
attached(): void {
this._itemService.getItems();
}
}
Here is the spec file:
import {TestElement} from "../../src/resources/elements/test-element";
import {ComponentTester, StageComponent} from "aurelia-testing";
import {ItemService} from "../../src/resources/services/item-service";
import {MockItemService} from "./mock-item-service";
import {bootstrap} from "aurelia-bootstrapper";
describe('test element', () => {
let testElement;
const path: string = '../../src/resources/elements/test-element';
beforeEach(() => {
testElement = StageComponent.withResources(path).inView(`<test-element></test-element>`);
testElement.bootstrap(aurelia => {
aurelia.use.standardConfiguration();
aurelia.container.registerTransient(ItemService, MockItemService);
});
});
afterEach(() => {
testElement.dispose();
});
it('should call mock item service', async() => {
await testElement.create(bootstrap);
expect(testElement).toBeTruthy();
})
});
But every-time the test is run, the console logs out the real service and not the mock. I have traced this to the aurelia-dependency-injection.js in the Container.prototype.get function. The issue seems to be around this section of code:
var registration = aureliaMetadata.metadata.get(aureliaMetadata.metadata.registration, key);
if (registration === undefined) {
return this.parent._get(key);
}
The registration object seems to be a bit odd, if it was undefined, the code would work as the correct dependency is registered on the parent and it would get the mock dependency. However, it is not undefined therefore it registers the real service in the DI container on this line:
return registration.registerResolver(this, key, key).get(this, key);
The registration object looks like this:
registration = TransientRegistration {_key = undefined}
Is this a bug in aurelia or is there something wrong with what I am doing?
Many Thanks
p.s. GitHub repo here to replicate the issue: https://github.com/Magrangs/aurelia-transient-dependency-issue
p.p.s Forked the DI container repo and added a quick fix which would fix my particular issue but not sure what the knock on effects would be. If a member of the aurelia team could check, that would be good:
https://github.com/Magrangs/dependency-injection/commit/56c7d96a496e76f330a1fc3f9c4d62700b9ed596
After talking to Rob Eisenberg on the issue there is a workaround for this problem. Firstly remove the #transient decorator on the class and then in your app start (usually main.ts) register the class there as a transient.
See the thread here:
https://github.com/Magrangs/dependency-injection/commit/56c7d96a496e76f330a1fc3f9c4d62700b9ed596
I have also updated the repo posted above: https://github.com/Magrangs/aurelia-transient-dependency-issue
to include the fix.
Hopefully this will help any other devs facing the same issue.
I am trying to learn how to test events emitted through a global Event Bus. Here's the code with some comments in the places I don't know what to do.
// EvtBus.js
import Vue from 'vue';
export const EvtBus = new Vue();
<!-- CouponCode.vue -->
<template>
<div>
<input
class="coupon-code"
type="text"
v-model="code"
#input="validate">
<p v-if="valid">
Coupon Redeemed: {{ message }}
</p>
</div>
</template>
<script>
import { EvtBus } from '../EvtBus.js';
export default {
data () {
return {
code: '',
valid: false,
coupons: [
{
code: '50OFF',
discount: 50,
message: '50% Off!'
},
{
code: 'FREE',
discount: 100,
message: 'Entirely Free!'
}
]
};
},
created () {
EvtBus.$on('coupon-applied', () => {
//console.info('had a coupon applied event on component');
});
},
methods: {
validate () {
// Extract the coupon codes into an array and check if that array
// includes the typed in coupon code.
this.valid = this.coupons.map(coupon => coupon.code).includes(this.code);
if (this.valid) {
this.$emit('applied');
// I NEVER see this on the coupon-code.spec.js
EvtBus.$emit('coupon-applied');
}
}
},
computed: {
message () {
return this.coupons.find(coupon => coupon.code === this.code).message;
}
}
}
</script>
// tests/coupon-code.spec.js
import expect from 'expect';
import { mount } from '#vue/test-utils';
import CouponCode from '../src/components/CouponCode.vue';
import { EvtBus } from '../src/EvtBus.js';
describe('Reminders', () => {
let wrp;
beforeEach(() => {
wrp = mount(CouponCode);
});
it('broadcasts the percentage discount when a valid coupon code is applied', () => {
let code = wrp.find('input.coupon-code');
code.element.value = '50OFF';
code.trigger('input');
console.log(wrp.emitted('applied'));
//
// I NEVER see this on the outpout.
// How can I test it through a global event bus rather than
// an event emitted from the component instance?
//
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
// Passes, but not using EvtBus instance.
expect(wrp.emitted('applied')).toBeTruthy;
});
});
So, my doubt is how to test that the global event bus is emitting and listening to events inside components that use that event bus.
So, is it possible to test the global Event Bus using Vue Test Utils or I should use another approach?
If component is using global EventBus, eg that's imported outside of given component and assigned to window.EventBus, then it's possible to use global Vue instance to redirect $on or $emit events to wrapper's vm instance. That way you can proceed writing tests as if component is emitting via this.$emit instead of EventBus.$emit:
it('clicking "Settings" button emits "openSettings"', () => {
global.EventBus = new Vue();
global.EventBus.$on('openSettings', (data) => {
wrapper.vm.$emit('openSettings', data);
});
// component emits `EventBus.$emit('openSettings')`
expect(wrapper.emitted('openSettings')).toBeTruthy(); // pass
});
Well,
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
This code in your spec file won't be called because the mounted wrp component is not using the same EvtBus you are importing in your spec file above.
What you require to test this is an npm package named inject-loader so that you can provide your own implementation(stub) of the EvtBus dependency of your coupon code component.
Somewhat like this
const couponCodeInjector = require('!!vue-loader?inject!src/views/CouponCode');
const stubbedModules = {
'../EvtBus.js': {
$on : sandbox.spy((evtName, cb) => cb());
}
};
const couponCode = couponCodeInjector(stubbedModules);
and then in your unit test you can assert whether the stubbedModules['../EvtBus.js'].$on has been called or not when code.trigger('input');
PS: I haven't used vue-test-utils. So I don't know exactly how to the stubbing with this npm package.
But the main thing you need to do is to find a way to stub your EvtBus dependency in the CouponCode component in such a way that you can apply a spy on it and check whether that spy has been called or not.
Unit tests should focus on testing a single component in isolation. In this case, you want to test if the event is emitted, since that is the job of CouponCode.vue. Remember, unit tests should focus on testing the smallest units of code, and only test one thing at a time. In this case, we care that the event is emitted -- EventBus.test.js is where we test what happens when the event is emitted.
Noe that toBeTruthy is a function - you need (). expect(wrp.emitted('applied')).toBeTruthy is actually not passing, since you need () - at the moment, it is actually doing nothing -- no assertion is made.
What your assertion should look like is:
expect(wrp.emitted('applied')).toBeTruthy()
You can go one step further, and ensure it was only emitted once by doing something like expect(wrp.emitted().applied.length).toBe(1).
You then test InputBus in isolation, too. If you can post the code for that component, we can work through how to test it.
I worked on a big Vue app recently and contributed a lot to the main repo and documentation, so I'm happy to help out wherever I can.
Let me know if that helps or you need more guidance. If possible, post EventBus.vue as well.
I got the same issue with vue-test-utils and Jest. For me, createLocalVue() of vue-test-utils library fixed the issue. This function creates a local copy of Vue to use when mounting the component. Installing plugins on this copy of Vue prevents polluting the original Vue copy. (https://vue-test-utils.vuejs.org/api/options.html#localvue)
Adding this to your test file will fix the issue:
const EventBus = new Vue();
const GlobalPlugins = {
install(v) {
// Event bus
v.prototype.$bus = EventBus;
},
};
// create a local instance of the global bus
const localVue = createLocalVue();
localVue.use(GlobalPlugins);
jest.mock('#/main', () => ({
$emit: jest.fn(),
}));
Include this in code in your spec file at the very begining.
Note: '#/main' is the file from which you are importing Event Bus.
I need to be able test my component (methods, computed properties, data, ...). However, when I import my vue component in my unit test:
import Pagination from 'src/components/shared/pagination.vue'
import { newComponent } from '../component-factory'
describe('pagination.vue', () => {
const propsData = {
metadata: {
page: 2,
records: 155,
total: 11,
},
perPage: 15,
}
it('should have correct number of records', () => {
const ctor = Vue.extend(Pagination)
const vm = new ctor({propsData}).$mount()
expect(vm.firstRecord).toBe(16)
expect(vm.lastRecord).toBe(30)
})
...
vm is of type Vue, and thus does not have the firstRecord/lastRecord properties. Running the test with karma shows a success, but the typescript compiler spits out Errors:
ERROR in ./tests/shared/pagination.spec.ts
(16,19): error TS2339: Property 'firstRecord' does not exist on type 'Vue'.
ERROR in ./tests/shared/pagination.spec.ts
(17,19): error TS2339: Property 'lastRecord' does not exist on type 'Vue'.
I tried casting:
...
const vm = new ctor({propsData}).$mount() as Pagination
...
But that results in a warning in VSCode:
[ts] Cannot find name 'Pagination'.
And has the effect of treating vm as type any which is totally counterproductive.
I think this all stems from the fact that when using .vue files you have to add the declaration:
declare module '*.vue' {
import Vue from 'vue'
export default typeof Vue
}
Which clearly sets the type of all .vue files to Vue, which isn't exactly a lie, but isn't helpful either... Any suggestions? What am I doing wrong?
For future reference, I have attempted to use vuetype which generates .d.ts files for each .vue file, but ran into this issue. Also, there is a request to make .vue a first class citizen in the typescript ecosystem, which would eliminate this problem. And, I just added a request for a vue language service extension
Up until Vue 2.5, their TypeScript documentation page recommended exporting an interface that extends Vue if you were not going to use vue-class-component. You can export this interface to use in your tests, to cast your component instance. The recommendation has been removed from the docs, but I have not been able to figure out how to change my tests to not need the interface.
It looks like vuetype could generate these interfaces for you, but I've just been creating them manually.
Here is a greatly simplified example, but you can define anything in your interface that you would reference on vm, ie data, props, methods:
// NOTE: Make sure your interface extends `Vue`!
export interface PaginationComponent extends Vue {
firstRecord: number,
lastRecord: number
}
export default {
name: 'Pagination',
data: function() {
return {
firstRecord: 16,
lastRecord: 30,
}
}
}
For your test, you can cast the component instance to the type of your exported interface:
import Pagination, {PaginationComponent} from 'src/components/shared/pagination.vue'
describe('pagination', () => {
it('should know about component data fields', () => {
const ctor = Vue.extend(Pagination)
const vm : PaginationComponent = new ctor().$mount()
expect(vm.firstRecord).toBe(16)
expect(vm.lastRecord).toBe(30)
})
})
I have uses angular translate from here (http://pascalprecht.github.io/angular-translate/) and it's just work fine, but it break my controller's unit test whith Error:
Unexpected request: GET scripts/i18n/locale-en.json
I don't understant why?
I use yeoman and test with karma.
app.js:
'use strict';
(function() {
angular.module('wbApp', ['authService', 'authUserService', 'checkUserDirective', 'ui.bootstrap', 'pascalprecht.translate'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl',
access: {
isFree: true
}
})
.when('/main', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
access: {
isFree: false
}
})
.otherwise({
redirectTo: '/'
});
});
})();
configTranslate.js:
'use strict';
(function() {
angular.module('wbApp')
.config(['$translateProvider',
function($translateProvider) {
$translateProvider.useStaticFilesLoader({
prefix: 'scripts/i18n/locale-',
suffix: '.json'
});
$translateProvider.preferredLanguage('en');
}]);
})();
karma.conf.js:
files = [
...
'app/bower_components/angular-translate/angular-translate.js',
'app/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
...
];
controller test:
'use strict';
describe('Controller: LoginCtrl', function() {
// load the controller's module
beforeEach(module('wbApp'));
var LoginCtrl, scope, location, httpMock, authUser;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, AuthUser) {
authUser = AuthUser;
location = $location;
httpMock = $httpBackend;
scope = $rootScope.$new();
LoginCtrl = $controller('LoginCtrl', {
$scope: scope
});
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
}));
it(...);
...
});
if i add this in test controller, product same error:
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(200);
httpMock.flush();
or
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.flush();
i find this post How do I test controllers with Angular Translate initialized in App Config? but not helped me :/
I extensively use $httpBackend in my tests and it works fine, but in this case it is ineffective. If I comment the line:
$translateProvider.preferredLanguage('en');
obviously an error, if I add on the runtime (in my controllers)
$translate.uses(local);
I end up with the same error?
So I turn to the translation configuration (configTranslate.js) or at runtime is the same result:
Unexpected request: GET scripts/i18n/locale-en.json
Here is the syntax that I tested, either in a "beforeEach(inject(function(...});"
or in a test "it('...', function() {...});"
httpMock.expectGET('scripts/i18n/locale-en.json');
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(data);
with at end
httpMock.flush();
I also tried a $ apply
httpMock.expectGET('scripts/i18n/locale-fr.json');
scope.$apply(function(){
$translate.uses('fr');
});
httpMock.flush();
nothing happens, Still this error is driving me crazy ..
If you have any suggestion
it's a known issue, please follow the documentation here: unit testing angular
The solution
Unfortunately, this issue is caused by the design of
angular-translate. To get around these errors, all we can do is to
overwrite our module configuration in our test suite, that it doesn't
use asynchronous loader at all. When there's no asynchronous loader,
there's no XHR and therefore no error.
So how do we overwrite our module configuration at runtime for our
test suite? When instantiating an angular module, we can always apply
a inline function which is executed as configuration function. This
configuration function can be used to overwrite the modules
configuration since we have access to all providers.
Using the $provide provider, we can build a custom loader factory,
which should then be used instead of the static files loader.
beforeEach(module('myApp', function ($provide, $translateProvider) {
$provide.factory('customLoader', function () {
// loader logic goes here
});
$translateProvider.useLoader('customLoader');
}));
Please read more in the above link provided.
We took the approach of ignoring the translation loader in unit tests, rather than being forced to modify each of the spec files.
One way to do it could be by separating the loader configuration to a separate file and then exclude it in karma.
So for example you can create a file app-i18n-loader.js (all other module configurations takes place in a different file):
angular
.module('myApp')
.config(loaderConfig);
loaderConfig.$inject = ['$translateProvider', '$translatePartialLoaderProvider'];
function loaderConfig($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'assets/i18n/{part}/{lang}.json'
});
$translatePartialLoaderProvider.addPart('myApp');
}
And in your karma.conf.js exclude the file:
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
//...
'bower_components/angular-translate/angular-translate.js',
'bower_components/angular-translate-loader-partial/angular-translate-loader-partial.js',
'app/**/*.mdl.js',
'app/**/*.js'
],
exclude: [
'app/app-i18n-loader.js'
],
(Note: Answer edited to a solution that does not require grunt/gulp).
I wanted a solution,
which was not too hacky
which didn't require me to change my actual application code,
which wouldn't interfere with the ability to load additional modules
and most importantly which wouldn't require me to change every
single test.
This is what I ended up with:
// you need to load the 3rd party module first
beforeEach(module('pascalprecht.translate'));
// overwrite useStaticFilesLoader to get rid of request to translation file
beforeEach(module(function ($translateProvider) {
$translateProvider.useStaticFilesLoader = function () {
};
}));
Assuming you don't need the actual translations for your unit tests, this works great. Just put the beforeEach on a global level, preferably in it's own file inside the test folder. It will be executed before every other test then.
I encountered this problem with protractor tests. My solution was to mock translations like this:
angular.module('app')
.config(function ($translateProvider) {
$translateProvider.translations('en', {});
$translateProvider.preferredLanguage('en');
})
Now no language files are downloaded, no strings get translated and I just test against the string keys in specifications:
expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');
Try putting to test method:
it('should ...', function() {
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond({});
httpMock.expectGET('scripts/i18n/locale-en.json');
scope.resetForm(); // Action which fires a http request
httpMock.flush(); // Flush must be called after the http request
}
See examples from Angular docs
Please have a look at https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/loader-static-files.spec.js as a reference.
In general, I would recommend using a standard translation loader for unit tests (without the hassle of http loadings) which means you can provide the labels with $translateProvider.translations(). Why? Because you do not have to test the remote loading functionality which is part of angular-translate project.
None of the solutions worked for me but I came with these solutions:
1) If you need to use scope.$apply(), or should deal with states in your test (after the $apply() the 2nd approach won't work), override your app's translations with the $translateProvider.translations() method, using a plugin to load JSON files
beforeEach(module(function ($translateProvider) {
$translateProvider.translations('en', readJSON('scripts/i18n/locale-en.json'));
}));
2) If your tested controller depends on the $translate service you can use a plugin to load JSON files and combine it with $httpBackend to load your locale file when angular-translate requests it.
beforeEach(inject(function (_$httpBackend_) {
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('scripts/i18n/locale-en.json').respond(readJSON('scripts/i18n/locale-en.json'));
$httpBackend.flush();
})));
Note this should be below your beforeEach(module('myApp')); or you will get an $injector error.
I made a simple mock service for $translate
$translate=function (translation) {
return {
then: function (callback) {
var translated={};
translation.map(function (transl) {
translated[transl]=transl;
});
return callback(translated);
}
}
};
Usage example here : https://gist.github.com/dam1/5858bdcabb89effca457
I use this pattern.
ApplicationModule set regular angular-translate config.
test code load 'testModule' instead of 'applicationModule'
// application module .js
(function() {
'use strict';
angular
.module('applicationModule', [
'ngAnimate',
'ngResource',
'ui.router',
'pascalprecht.translate'
])
.config(['$stateProvider', '$urlRouterProvider', '$translateProvider', '$translatePartialLoaderProvider', config]);
function config($stateProvider, $urlRouterProvider, $translateProvider, $translatePartialLoaderProvider) {
// set routing ...
$translateProvider.useStaticFilesLoader({
prefix: 'i18n/locale-',
suffix: '.json'
});
$translateProvider.useMessageFormatInterpolation();
$translateProvider.fallbackLanguage(['en']);
$translateProvider
.registerAvailableLanguageKeys(['en', 'ko'], {
'en_US': 'en',
'ko_KR': 'ko'
})
.determinePreferredLanguage(navigator.browserLanguage);
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
$translateProvider.useSanitizeValueStrategy('escaped');
}
})();
// test.module.js
(function() {
'use strict';
angular
.module('testModule', ['applicationModule'])
.config(['$translateProvider', '$translatePartialLoaderProvider', config])
.run(['$httpBackend', run]);
function config($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'i18n/locale-en.json'
});
$translatePartialLoaderProvider.addPart('applicationModule');
}
function run($httpBackend) {
$httpBackend.when('GET', 'i18n/locale-en.json').respond(200);
}
})();
// someDirective.spec.js
describe("a3Dashboard", function() {
beforeEach(module("testModule"))
var element, $scope;
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
element = angular.element("<div>{{2 + 2}}</div>");
$compile(element)($rootScope)
}))
it('should equal 4', function() {
$scope.$digest();
expect(element.html()).toBe("4");
})
})
Late to the table with this, but I got round this by specifying that Karma simply serve the files as per this entry in karma.conf.js:
files: [
...
{pattern: 'scripts/i18n/*.json', included: false, served: true},
...
]
The 2016 answer for this is to preprocess your json into your tests and properly test translations work on your directives.
I use karma-ng-json2js-preprocessor. Follow all the steps to setup your karma.conf then in your test file, prepend the relevant file as a module, then set that information in $translateProvider.
beforeEach(module('myApp', '/l10n/english-translation.json'));
// Mock translations for this template
beforeEach(module(function($translateProvider, englishTranslation) {
$translateProvider.translations('en_us', englishTranslation);
$translateProvider.useSanitizeValueStrategy(null);
$translateProvider.preferredLanguage('en_us');
}));
Note according to the plugin, it uses your filename to generate a camelcased module name. You can play with the function inside the module's /lib but basically it remove all dashes but KEEPS underscores in a camelCase. So en_us becomes En_us.
You'll also need to tell your test that it is expecting that file as a GEt.
$httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);