Angular2 Component: Testing form input value change - unit-testing

I have a text input and i'm listening for the changes.
mycomponent.ts
ngOnInit() {
this.searchInput = new Control();
this.searchInput.valueChanges
.distinctUntilChanged()
.subscribe(newValue => this.search(newValue))
}
search(query) {
// do something to search
}
mycomponent.html
<search-box>
<input type="text" [ngFormControl]="searchInput" >
</search-box>
Running the application everything works fine, but i want to unit-test it.
So here's what i tried
mycomponent.spec.ts
beforeEach(done => {
createComponent().then(fix => {
cmpFixture = fix
mockResponse()
instance = cmpFixture.componentInstance
cmpFixture.detectChanges();
done();
})
})
describe('on searching on the list', () => {
let compiled, input
beforeEach(() => {
cmpFixture.detectChanges();
compiled = cmpFixture.debugElement.nativeElement;
spyOn(instance, 'search').and.callThrough()
input = compiled.querySelector('search-box > input')
input.value = 'fake-search-query'
cmpFixture.detectChanges();
})
it('should call the .search() method', () => {
expect(instance.search).toHaveBeenCalled()
})
})
Test fails as the .search() method is not called.
I guess i have to set the value in another way to have the test realize of the change but i really don't know how.
Anyone has ideas?

It might be a little bit late, but it seems that your code is not dispatching input event after setting input element value:
// ...
input.value = 'fake-search-query';
input.dispatchEvent(new Event('input'));
cmpFixture.detectChanges();
// ...
Updating input html field from within an Angular 2 test

Triggering the value change of FormControl is as simple as:
cmpFixture.debugElement.componentInstance.searchInput.setValue(newValue);

Custom component with #input, subscriptions, two way data binding
If you got a custom component you would need further changes in your application to be able to successfully unit test your application
have a look at the gist here this will give you some idea
https://gist.github.com/AikoPath/050ad0ffb91d628d4b10ef81736af386/raw/846c7bcfc54be8cce78eba8d12015bf749b91eee/#ViewChild(ComponentUnderTestComponent).js
More over complete reading over here carefully otherwise you can easily get confused again -
https://betterprogramming.pub/testing-angular-components-with-input-3bd6c07cfaf6

Related

Mocking BsModalRef for Unit Testing

I am using the BsModalRef for showing modals and sending data using the content property. So we have some like this :
this.followerService.getFollowers(this.bsModalRef.content.channelId).subscribe((followers) => {
this.followerList = followers;
this.followerList.forEach((follower) => {
follower.avatarLink = this.setUserImage(follower.userId);
this.followerEmails.push(follower.email);
});
});
We are setting the channelId in content of bsModalRef (this.bsModalRef.content.channelId). It is working fine. Now i am writing a unit test for this. Problem is i am not able to mock it. I have tried overriding, spy etc but nothing seems to work. I am using the approach mentioned in this link. One alternative is to use TestBed but i am not much aware of its use. Can anyone please help me finding any approach by which this can be achieved ?
I recently had to do something similar and Mocking the method call worked. The tricky part is injecting the BsModalService in both the test suite and the component.
describe('MyFollowerService', () => {
configureTestSuite(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [...],
providers: [...]
}).compileComponents();
});
// inject the service
beforeEach(() => {
bsModalService = getTestBed().get(BsModalService);
}
it('test', () => {
// Mock the method call
bsModalService.show = (): BsModalRef => {
return {hide: null, content: {channelId: 123}, setClass: null};
};
// Do the test that calls the modal
});
});
As long as you're calling bsModal as follows this approach will work
let bsModalRef = this.modalService.show(MyChannelModalComponent));
Finally, here are some links that have more indepth coverage about setting up the tests with TestBed.
https://chariotsolutions.com/blog/post/testing-angular-2-0-x-services-http-jasmine-karma/
http://angulartestingquickstart.com/
https://angular.io/guide/testing

How to Test a Global Event Bus With Vue Test Utils?

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.

.forEach within async action creator not returning action when running unit test

I am using react/redux to generate a list of panels, each of which displays data on each list item. I set a 5 second interval that calls refreshAppList(this.props.list) action creator that forEach loops through every item in the list and makes an async call which then dispatches the refreshed list item (using redux-thunk). So basically, every 5 seconds I am refreshing the list of panels with the most up-to-date data. This works great! Unfortunately, now that I am writing unit tests for this particular async action creator I have run into an issue. .forEach does not return anything so when I call it in my unit tests I am getting undefined. Does anyone know how to override this issue or maybe i need to use a different method to refresh the entire list of panels?
Here is the action creator that is looping through the array and making an async call on each array item.
export const refreshAppList = list => (dispatch) => {
list.forEach((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
});
};
Here is the error i am receiving:
1) async actions creates an action with type: REFRESH_APP_LIST:
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (tests/asyncActions.js:140:12)
Here is where I am calling the action creator within the test (using redux-mock-store):
return store.dispatch(refreshAppList(list)).then(() => {
expect(store.getActions()).to.deep.equal(expectedActions);
});
I think it is also worth mentioning that I am using axios-mock-adapter to mock the data returned from the async call within the action creator.
One last thing: I have written unit tests for two other async action creators within the same app and both pass. The big difference is that this particular action creator is chaining together multiple async calls using a forEach loop (that is not returning anything to the test).
That doesn't work because the function that refreshAppList returns doesn't return anything. Also, .forEach doesn't return anything even though you do return axios.get. from inside. You could use .map instead and return everything inside Promise.all. Something like this
export const refreshAppList = list => (dispatch) => {
return Promise.all(list.map((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
}));
};

Testing basic navigation in Angular 2 with fakeAsync() instead of Jasmine's done()

I currently have a working test using Gherkin steps and Jasmine async done() for basic navigation as such:
User_Navigates_To_URL(urlPath: string) {
return this.router.navigateByUrl(urlPath);
}
User_Will_Be_On_URL(expectedPath: string) {
expect(this.location.path()).toBe(expectedPath);
}
it('Scenario: User should be able to navigate', (done) => {
When.User_Navigates_To_URL('/associates/your-team').then(() => {
Then.User_Will_Be_On_URL('/associates/your-team');
});
done();
});
But what I'm trying to accomplish is to write this test using fakeAsync instead of jasmine's done() method. This way all async actions will be resolved in the zone and I wont have to nest the assertion step as a callback of the promise. Therefore, I'm attempting to do something like so:
it('Scenario: User should be able to navigate', <any>fakeAsync(() => {
When.User_Navigates_To_URL('/associates/your-team');
tick();
Then.User_Will_Be_On_URL('/associates/your-team');
}));
After weeks of research, the only thing close I found helpful was this question: Does fakeAsync guarantee promise completion after tick/flushMicroservice. But even when I've tried to implement my navigation promise in his snippet, it never resolves.
User_Navigates_To_URL(urlPath: string) {
let currNav:Promise<Router> = this.router.navigateByUrl(urlPath);
let handleNavigation = function (p:Promise<Router>) {
p.then(() => {
console.log('navigation complete')
});
};
let p = Promise.resolve(currNav);
handleNavigation(p);
tick();
}
This is my first question on here so please let me know if my quesiton is confusing or if I need to provide any more details.

How do I trigger a keyup/keydown event in an angularjs unit test?

I want to unit test a directive that emulates a placeholder, where the input value is cleared only on keyup/down events.
You need to create an event programatically and trigger it. To do so including jQuery for unit tests is quite useful. For example, you could write a simple utility like this:
var triggerKeyDown = function (element, keyCode) {
var e = $.Event("keydown");
e.which = keyCode;
element.trigger(e);
};
and then use it in your unit test like so:
triggerKeyDown(element, 13);
You can see this technique in action in the http://angular-ui.github.io/bootstrap/ project here: https://github.com/angular-ui/bootstrap/blob/master/src/typeahead/test/typeahead.spec.js
Disclaimer: let's be precise here: I'm not advocating using jQuery with AngularJS! I'm just saying that it is a useful DOM manipulation utility for writing tests interacting with the DOM.
To make the above code work without jQuery, change:
$.Event('keydown')
to:
angular.element.Event('keydown')
I had issues with using accepted answer. I found other soultion.
var e = new window.KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
shiftKey: true
});
delete e.keyCode;
Object.defineProperty(e, 'keyCode', {'value': 27});
$document[0].dispatchEvent(e);
Working example can be found here
I got something like this working.
element.triggerHandler({type:"keydown", which:keyCode});
if you are using angular2, you can trigger any event by calling dispatchEvent(new Event('mousedown')) on HTMLElement instance. for example: Tested with angular 2.rc1
it('should ...', async(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
return tcb.createAsync(TestComponent).then((fixture: ComponentFixture<any>) => {
fixture.detectChanges();
let com = fixture.componentInstance;
/* query your component to select element*/
let div:HTMLElement = fixture.nativeElement.querySelector('div');
/* If you want to test #output you can subscribe to its event*/
com.resizeTest.subscribe((x:any)=>{
expect(x).toBe('someValue');
});
/* If you want to test some component internal you can register an event listener*/
div.addEventListener('click',(x)=>{
expect(x).toBe('someOtherValue');
});
/* if you want to trigger an event on selected HTMLElement*/
div.dispatchEvent(new Event('mousedown'));
/* For click events you can use this short form*/
div.click();
fixture.detectChanges();
});
I recently wanted to test this HostListener on a component (Angular 2):
#HostListener('keydown.esc') onEsc() {
this.componentCloseFn();
};
And after searching for some time, this is working:
..
nativeElement.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Escape'}));
...