Categories
Programming

Unit Tests in Angular 2

Recently at my new company, Spigit, I’ve started on working on unit testing. Now, previously I’ve done unit testing and end to end testing using NighwatchJS at Walmart Labs.
And now with my new project, I have to use Angular 2.

After working with Angular 2 for a few months now and ReactJS last year, my feelings towards the two are: ReactJS is more flexible and lightweight, and Angular 2 comes with more out of the box features. It’s kind of like the old BackboneJS vs Angular 1 argument. Do you want a more lightweight, flexible framework, or do you want to go all in with one?

With ReactJS, you only have the “view” layer and you still have to use Flux or Redux to actually make service or API calls. Then you have to add Flow for type checking.
With Angular 2, you have that all out of the box with a comprehensive library + TypeScript which has built in type checking and is ES6 natively (but not exactly the same as ES6 as I found out later on). But Angular 2 is more heavy and you might not be using its full capability, so it’s not as flexible as ReactJS. It’s just like how Angular 1.x provided everything out of the box whereas BackboneJS required you to use UnderscoreJS, jQuery etc on top of it (which some companies expanded upon more with MarionetteJS, HandlebarsJS, EpoxyJS, ThoraxJS, Lodash etc adding various capabilities onto Backbone).

Out of the two, ReactJS has a higher learning curve I feel. Having the markup in the JS files (JSX) and dealing with the component lifecycle and passing in props is a very different way of coding than the past.
Angular 2 (and Angular 4, etc) is also very different than AngularJS (that’s Angular 1.x btw), but its not quite the same chasm of difference that React is. AngularJS devs can migrate to Angular 2 fairly easily as there’s a guide for it, it still uses templates just like Backbone or AngularJS and you still have things like directives and pipes, but now everything is a component (let’s face it we live in an ES6 world now and you should be familiar with ES6 and dealing with importing/exporting components instead of the old MVC structure). Other AngularJS functions like `$scope.apply` have equivalents in Angular 2 like `changeDetectorRef.detectChanges()` But still out of the two new age popular frameworks, Angular 2 is easier to pick up. I technically prefer VueJS to either of them but it’s not quite as popular yet.

So my last 6 months with Walmart Labs I was mostly doing end to end testing using the NightwatchJS framework which uses the Selenium driver to automate browser testing. It puts less pressure on manual QA testing. We did testing on four major browsers: IE11, Firefox (last 3 versions), Chrome (last 3 versions), and Safari. We found some quirks with testing in certain browsers. For example on IE11, we would have to use clearValue twice to actually clear a value.


// For IE11
client.clearValue('input[type=text]');
client.clearValue('input[type=text]');

Which was weird, and also oddly enough on Firefox, clicking a button wouldn’t work unless the button was actually in the viewport. So we have to scroll down to it first.


// For Firefox
client.moveToElement('#main', 10, 10);
client.click("#main ul li a.first");

Very odd issues. But most people use Chrome as the standard and we only noticed the other failures since we integrated our CI with Admiral and SauceLabs.

Out of E2E testing and unit testing, Both are important, but when choosing which one to start out with, I feel unit testing is the more fundamental one to start out with. If you have a great QA team already, then you can make do without E2E testing since all it really does is automate integration testing. Unit tests also help catch regressions but on a more fundamental component level, whereas E2E catches regressions on the UI level. QAs can actually write E2E tests. But QAs usually don’t write unit tests; that’s the developers job since they know their own code the best.

ReactJS uses Mocha for unit testing (and Sinon for spies), while Angular uses Jasmine which come with their own spies. So starting out on Angular 2’s testing guide, its a big long ass intimidating page to read, and there’s tons of testing functions in there. I recommend reading the first half as the latter half are for very niche scenarios. There’s also a great plnkr link at the bottom of the page which contains ALL the unit tests in their whole, and I recommend looking at that because just showing code snippets is really not good enough. Sometimes you might be missing imports or stubs or declarations that you don’t see in the code snippets.

One of issues I ran into on the guide is using stubs vs mock classes. When we are testing components, we have to choose between using one of those if we have a function that reaches out to a service. We don’t want to call the real service. So, if we want more flexibility we can use a mock class, but the problem is Angular’s example doesn’t really work for me… when I put in the mock class as a provider using `useClass` and then using `overrideComponents`, Angular complains about it in my IDE.. I posted a question on stack overflow about it, but no one seems to reply.
So, I’m stuck using stubs instead, which is fine if the component doesn’t do too many service calls, but you lose out on the power of mock classes, but oh well. With Stubs we have to use `useValue` instead. Oh, and another gotcha I encountered… Jasmine spies by default only intercept the function but don’t call through to the real function. You have to use `spyOn(foo, ‘bar’).and.callThrough();` to actually go into the real function, which is useful when you have multiple spies on functions that call other functions.

Now I know there’s a lot of Angular 2 testing articles out there… and a lot of them have different strategies.. this is the one that worked for me…


// In addition to importing all the modules and services / providers you are using here, you need the core libraries...
import { fakeAsync, tick, async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';


describe('HeaderComponent', () => {

let comp: HeaderComponent;
let fixture: ComponentFixture;
let sessionServiceStub: any;
let utilsServiceStub: any;

beforeEach(async(() => {
utilsServiceStub = {
getPageName: jasmine.createSpy('getPageName').and.callFake(
() => ''
)
};
sessionServiceStub = {
getUserInfo: jasmine.createSpy('getUserInfo').and.callFake(() => {
return {
displayName: 'Jack'
};
}),
getSiteInfo: jasmine.createSpy('getSiteInfo').and.callFake(
() => Promise.resolve(true).then(() => {})
)
};
TestBed.configureTestingModule({
declarations: [HeaderComponent],
imports: [TranslateModule.forRoot()],
providers: [
{ provide: SessionService, useValue: sessionServiceStub },
{ provide: UtilsService, useValue: utilsServiceStub }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
comp = fixture.componentInstance;
comp.nav = [
{
pageName: 'Home',
tabLabel: 'tablabels.home'
},
{
pageName: 'PostIdea',
tabLabel: 'tablabels.post_idea'
},
{
pageName: 'ViewIdeas',
tabLabel: 'tablabels.view_ideas'
}
];
});

describe('component onInit', () => {
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});

it('should init the header logo', () => {
const headerLogoEl = fixture.debugElement.query(By.css('.header-logo'));
expect(headerLogoEl.nativeElement.textContent).not.toBeNull();
});

it('should init the header menu', () => {
const headerMenuEl = fixture.debugElement.query(By.css('.header-menu'));
expect(headerMenuEl.nativeElement.textContent).not.toBeNull();
});

it('should init the right services on init', () => {
fixture.detectChanges();
expect(utilsServiceStub.getPageName.calls.count()).toBe(1);
expect(sessionServiceStub.getUserInfo.calls.count()).toBe(1);
expect(sessionServiceStub.getSiteInfo.calls.count()).toBe(1);
});
});

describe('buildMenu()', () => {
it('should init the menu holder', () => {
const navBarEl = fixture.debugElement.query(By.css('.navbar-spigit'));
spyOn(comp, 'buildMenu');
navBarEl.triggerEventHandler('showMoreMenu', null);
expect(comp.buildMenu).toHaveBeenCalled();
const menuHolder = fixture.debugElement.query(By.css('.more-menu-holder'));
const menuHolderContent = menuHolder.nativeElement.textContent;
expect(menuHolderContent).not.toBeNull();
});

it('should show dropdown menu when moreMenuConfig is visible', () => {
let dropdownMenu = fixture.debugElement.query(By.css('.dropdown-menu'));
expect(dropdownMenu).toBeNull();
comp.moreMenuConfig = {
visible : true,
mouseOutIntervalTime : 300,
mouseOutInterval: null,
moreMenuButtonVisible: true
};
// dropdown menu should be visible now
fixture.detectChanges();
dropdownMenu = fixture.debugElement.query(By.css('.dropdown-menu'));
expect(dropdownMenu).not.toBeNull();
});
});
});

and if you’re using Angular Bootstrap.. it actually does async stuff under the hood so you have to be careful about selecting DOM elements if they are using Bootstrap…


it('should call onScroll when scrolling on menu', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => { // wait for promise to return
fixture.detectChanges();
const menuEl = fixture.debugElement.query(By.css('.dropdown-menu'));
menuEl.triggerEventHandler('scroll', {
target: {
scrollHeight: 100,
scrollTop: 0,
clientHeight: 0
}
});
expect(comp.onScroll).toHaveBeenCalled();
expect(comp.getCommunities).toHaveBeenCalled();
})
}));

And sometimes when you are testing service calls, you also want to test for functions or code that executes in the callback of the promise as well… then you need `fakeAsync` for the job:


it('should get mini profile', fakeAsync(() => {
nameElmt.triggerEventHandler('mouseover', null); // mouseover triggers async service call
fixture.detectChanges();
tick(); // this essentially forwards the promise to the 'then' callback function
expect(userServiceStub.getMiniProfile.calls.count()).toBe(1);
}));

That’s just some sample code for testing components… now for testing services it gets more difficult since the Angular 2 testing guide doesn’t explain this very thoroughly… this is how I did it


import * as data from './data.json';

Now in TypeScript we can’t actually import json without it complaining about it missing a module definition.. unlike in native ES6. So we have to use a workaround hack .. we have to create another separate file in the same directory as the json file with the same name (including .json) so we have a file called `data.json.ts` and inside it the only content we have is

export default '';

And that’s it! Now the importing works. It’s weird but yes we have to do that until TypeScript natively supports importing JSON.

then in the test file…


describe('PageService', () => {

let utilsServiceStub: any;
const mockResponse:any = data; // Typescript will not recognize anything in the json data unless we force a type on it

beforeEach(async(() => {
utilsServiceStub = {
getPageName: jasmine.createSpy('getPageName').and.callFake((param: string) => '')
};
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
PageService,
MockBackend,
{ provide: UtilsService, useValue: utilsServiceStub },
{ provide: XHRBackend, useClass: MockBackend }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
}).compileComponents();
}));

describe('getPagePromise()', () => {
it ('should return appropiate response', async(
inject([PageService, MockBackend], (pageService, mockBackend) => {

mockBackend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(mockResponse)
})));
});

pageService.getPagePromise().then((pageData) => {
expect(utilsServiceStub.getPageName.calls.count()).toBe(1, 'getPageName called once');
expect(pageData.ideaStages.length).toBe(6);
expect(pageData.numLifecycleStages).toBe(7);
expect(pageData.creatorName).toEqual('Viviana Arandia');
expect(pageData.ideaTitle).toEqual('ONE POPULAR STAR WARS PLANET THAT ALMOST SHOWED UP IN ROGUE ONE');
expect(pageData.hasViewed).toBe(false);
});

})));
});
});

Sometimes … there is a function that deals with DOM and things become very difficult to test with native functions like setTimeOut() for example.


keyPressHandler(event): void {
if (event.keyCode === 13 && this.searchInput.nativeElement.value) {
this.submitSearch();
}
}

clearMoreBtnTimeout() {
clearInterval(this.moreMenuConfig.mouseOutInterval);
}

In this example, its hard to test because we can’t simulate native events easily or native DOM elements that easily. Instead what we can do is pass in an optional test variable that denotes its being used for a test, and that way we can reach better code coverage without having to deal with potentially nasty DOM/event simulations.


keyPressHandler(event, test?): void {
if (event.keyCode === 13 && this.searchInput.nativeElement.value) {
this.submitSearch();
} else if (test) {
this.submitSearch();
}
}

clearMoreBtnTimeout(test?) {
// clear morebutton over timeout
if (!test) {
clearInterval(this.moreMenuConfig.mouseOutInterval);
} else {
this.moreMenuConfig.mouseOutInterval = 0;
}
}

Ok, now we can test it like this:


describe('clearMoreBtnTimeout()', () => {

it('should set mouseOutInterval to 0', () => {
component.clearMoreBtnTimeout(true);
expect(component.moreMenuConfig.mouseOutInterval).toEqual(0);
});

});

describe('keyPressHandler()', () => {

it('should call submitSearch', () => {
spyOn(component, 'submitSearch');
component.keyPressHandler({}, true);
expect(component.submitSearch).toHaveBeenCalled();
});

});

And just like that, we can up our code coverage for those pesky sections of code that were hard to unit test before.

So those are my component and service tests… let me know if that helps anyone! the existing Angular 2 unit testing articles are very confusing and contradictory for me so.. this is what worked for me.

Categories
General

Work, gaming, Paris attacks

Well, I haven’t seen sunlight in about 2 weeks. Seriously, I may be becoming a vampire now.
I’ve been working pretty hard lately, fixing bugs late into the night. Just a few days ago, I worked 14 hours and fixed 13 bugs in one day. I’ve learned alot about using AngularJS, and specifically dealing with promises and promise handlers, using the Angular $q library (which is different than Kriskowals Q library), not using the $q at appropiate times, and dealing with error and success handlers in the service vs controller layers. This is kind of saddening because we are transitioning to using the Facebook React framework instead and its completely different. I admit it looks pretty daunting and about half of it is because ECMA6 is just so different than ECMA5, it has a bunch of new functional notations which I am not used to at all.

That aside, I’ve been playing Fallout 4 and Starcraft 2: Legacy of the Void with the little free time that I do get, and I love those games. Seriously, I bought the collectors editions for both. That’s only something I do with games I really like. I have the collectors editions for SC2: Heart of the Swarm, Diablo 3: Reaper of Souls, Blazblue Chronophantasma, Persona 4 Dancing all night… and thats pretty much it. I did unboxing videos on youtube for all of these, and will do for F4 and SC2 LOV as well next time I get back to San Francisco. Oh, and Hearthstone (now with the League of Explorers expansion), which I always play in short bursts. Oh, and Star Wars: Battlefront and Assassins Creed Syndicate aren’t bad either, though I play them slightly less than SC2 and F4. Is this a blockbuster gaming holiday season or what?

httpv://www.youtube.com/watch?v=xWEtGv7HhnE

httpv://www.youtube.com/watch?v=8PvMSWhbQBk

I have been in Korea for almost a year now, which is crazy. I never thought it would pass by this fast. I will put my thoughts on this year overall in my end of year review, but for now I have to look for a new place, cause the current place I’m living now is too expensive. I also had thoughts about moving back to California, or someplace else given that I could work from anywhere, but the pros-cons of the situation came down to this: Korea has cheaper cost of living, better nightlife, considerably more beautiful women, and fast convenience for everything. The cons is that I don’t speak the native language well, its hard to make friends/lasting relationships here, and my working schedule is nocturnal here. The pros of the US/Canada is that I have the convenience of speaking English having more accessible healthcare, my friends there are more free and have more time to meet, and California weather is definitely better (but not Canada’s). Though, in the end my primary concern is making friends and relationships, and thats pretty hard anywhere as I found out, and not that much easier in California/Canada either as I experienced last year. I might be lonely in Korea, but I was also lonely in California last year. So… continue for one year more in Korea and then I will re-evaluate the situation again next year.

And the other thing that went on recently is the Paris terrorist attacks, which is all over the news and Facebook. I commented on this in Facebook, and being the type of person that doesn’t always post positive opinions on everything, I said:

Guys, while the attack on Paris is surely terrible, these kind of terrorist attacks happen all the time in the middle east, pakistan, and just a few months ago in Tunisia. In fact there was one that happened right before Paris at Beirut! I don’t think those ever get as much coverage as attacks on Western countries. Listen, I know people losing their lives is awful, but I don’t believe that anyone losing their life in Paris is *more* awful than someone losing their life somewhere else.

Everyone’s death is equally awful. It was the same when the Boston marathon bombings happened, and a Chinese earthquake happened around the same time which killed 10,000 Chinese. Which one got more coverage? Oh right, the 3 people that died in America. We should strive to get equal news coverage of these events, not just the events that happened in the “more important countries”. I don’t think thats fair.

Right now, people around the world are ‪#‎prayingforParis‬ and changing their Facebook pics if something like this happened in China or India do you think people around the world would ‪#‎prayforChina‬ and change their profile pics to communist red? I don’t think so.

And I stand by that statement. Its ridiculous that people are all changing their profile pics to french colors. What does that do? It doesn’t bring back anyone who died, and it serves no purpose other than the fact that ok, you read the news and you feel for the people who lost their loved ones. Ok, well that just means you didn’t care about all the other times people lost their loved ones in other terrorist attacks that happened in the world. Cause you didn’t change your profile pics those times. But this time is special right? Cause it happened in an “important country”, like USA, UK, France, Germany, etc this just validates my belief that people care way more about media events in Western countries than other countries in the world. Everyone deserves equal coverage.

Categories
Programming

Startup Weekend San Francisco 2012

Wow what a weekend it has been. I attended Startup Weekend SF, the second SW I’ve been to, and it was a blast.

Edit: Apparently this is the app that won at Startup Weekend Google. Similar idea, different name? Interesting.

Team SplitMyTab
Team SplitMyTab

Team
My team consisted of me (the frontend engineer), a designer, a business person and two backend engineers. Our App idea was called SplitMyTab, which helps people with all the trouble of calculating and figuring our their restaurant bills when tips, tax, credit cards, and cash all give people headaches when they split the bill. As you can see, we actually won honorable mentions in the competition for good storytelling and customer validation (as well as a free year of SendGrid service), hence the medal :).

App details
While the idea was simple, the app was actually difficult to implement. When the app is loaded, it asks you to take a picture of the bill using the camera. This part required iOS and native functionality. After this, our image OCR algorithm done in Python processes the image and extracts the data out and sends it to our Ruby on rails service, which stores it. The Ruby service uses templating along with Bootstrap and jQuery Mobile to display the web/mobile site.

Web/Mobile
Since I was the front end engineer, I was in charge of doing the web/mobile stuff. So I used HTML5 Boilerplate (I could’ve also used Bootstrap) and jQuery Mobile, along with jQuery UI and Backbone.js to support the JS animations and structure, respectively.

So initially we thought about using the phone’s contacts / numbers as a way to tell users what they owe, using PhoneGap or iOS to access native features, and Twilio for SMS, but turns out no one really knew PhoneGap or Objective C, and not enough time to figure out in one weekend.

So, we decided to go with Facebook API to message/email people.

Initial screen
Initial screen

We force users to login to Facebook on the initial page, with the photo that they’ve taken shown, and assuming that the image data from the receipt is available. Then we land on the calculate totals page, which calculates the total of the bill including tips that you enter. There’s also an autocomplete which pulls from your facebook friends list, who we assume you are eating with.

Calculate totals page
Calculate totals page

After that, we go to the final page, which is the page where we calculate who ate what and what the final totals were.

Receipt Totals Page
Receipt Totals Page

And after you confirm, it sends a message to all your facebook friends who ate with you telling them how much they owe you.

Issues
First, technology decisions. I chose jQuery Mobile over Sencha because it relies all on HTML5 page-roles and semantics, which is really nice and easy to learn, whereas Sencha has a learning curve. I used Backbone.js over AngularJS because Angular would’ve required more time to learn the syntax.

Interesting issues:
1) We couldn’t figure out a good mechanism in time for the Python program to communicate with our Rails server.
2) I figured out too late that jQuery mobile has quirks with dynamically inserted html, and should’ve used Ruby templating instead. I don’t know any Ruby so I had to work with just HTML5 without any templating engine like PHP/Rails.
3) jQuery mobile apparently doesn’t execute all the JS after an Ajax form submission. Actually, all of its page changes uses Ajax.
4) jQuery mobile fires its mobile initializing way before the document is ready, this causes problems with dynamically generated DOM elements.
5) Facebook API apparently deprecated its legacy REST API, and its Graph API doesn’t have a good way to message or email friends.
6) jQuery mobile apparently doesn’t play nicely with jQuery UI.
7) jQuery mobile also doesn’t like it when you put custom JS page init events in the main data-role=page div.
8) Always JSON stringify / parse objects when storing and retrieving from localStorage
9) DOM elements from a jQuery array are not jQuery objects themselves and have to be re-wrapped in a jQuery selector. This differs from Prototype.js which always wraps each sub-element in its selector.
10) Facebook API has terrible documentation. Seriously.

So yeah, I learned alot of interesting quirks about jQuery Mobile and Facebook API over the weekend. Next time, maybe I’ll try a different mobile framework, and use Zepto.js instead, a lighter weight jQuery alternative. And try using AngularJS, because Backbone requires a lot of boilerplate code to be written, though I like its use of MVC and SoC patterns. And yes, I’ll consider looking into SASS, Node.js, Socket.io and CoffeeScript (there’s too much stuff to learn blah). Overall I learned a lot, which is all I really wanted to get out of these events anyway. Take a break from CFM (coldfusion) and FTL (freemarker) for a while.

Where’s the app code?
Relax, the Github repo is here, and the prototype mobile site I’ve built is here (it’s not fully functional because of the jQuery mobile quirks).