Paul Hammant's Blog: A lower technology style of Angular for the enterprise
November 2015 Update: Greyhound.com changes their URLs - still ASP.NET, but very different GET-centric URLs whereas two years ago, this was POST-centric and I had to do a lot of hacking. TL;DR: don’t expect this project’s build to pass any more :(
I have been playing with a use of Angular that doesn’t follow the recommended Grunt/Bower/NPM best-practice tool-chain. The hypothesis is that many Java enterprises are not ready to go all the way down that road. It could be that developers are more comfortable with Java IDEs and build tools. It could be that the infrastructure people are not ready upgrade CI tools to be able to deal with Grunt and all that. I’m also trying to see if there’s a faster way of making Angular applications, having been part of teams that were faster with old (worse) technologies.
In this article, I outline a tier above greyhound.com’s booking service:
I’m using TestNG, but JUnit would be just fine as an alternative to that. It could also as easily be .Net & NUnit or Ruby & RSpec too.
Bus tickets borrowing greyhound.com’s services
I’ve borrowed the “Web 1.5” app hosted on www.greyhound.com. It is currently written in .Net and uses commercial controls for some of the heavy lifting (Telerik). I’ve only delivered two components - Search Criteria, and Search Results. As this is totally unapproved usages of their services, you can’t actually buy tickets. I think if I went that far, I’d be getting a visit from the police. Greyhound could easily disable “support” for this proof of concept by checking the referrer for incoming service requests
These are not a strong recommendation, it’s just what I chose to support the idea of Component Testing in a browser for a hypothetical enterprise dev team. The exercise could have been as easily done in .Net or Ruby.
- Plain Java Servlets/Filters
- Google’s HTTP client (needs a snappy name) - to interop with www.greyhound.com
My dev machine’s setup
I’ve installed a DNS tool (dnsmasq) to my Mac. It allows me to map all of *.dev to 127.0.0.1. That allows me to open one page in Firefox (WebDriver) and have a different domain per test method, yet keep the same browser window open. I no longer have to worry about DOM state from one test leaking into another. I had this with ports changing in an earlier version, but figured domains was a better solution. Here’s the blog entry that speeded me through the setup for it. In the videos of the UI tests, you can see the domain name changing per test.
Other than that I’m using Jetbrains’ Intellij IDEA - the Rolls Royce of IDEs for 13 years.
Maven acquired dependencies:
- FluentSelenium & ngWebDriver (I’m leading those)
- Selenium-WebDriver (transitively)
- Moco (award winning, and by ThoughtWorks Colleague Zheng Ye).
- TestNG (with Guice for Dependency Injection)
- Hamcrest for assertions
- Maven (though could as easily be Gradle).
Update: Moco is now using released artifact (v0.9), whereas I used to have a dependency on an unreleased version.
Selenium-WebDriver as a crutch
It was true for Selenium 1.0, and it is still a classic mistakes with WebDriver (Selenium 2.0):
- Going through a long path to get to the component under test (CUT) - specifically login and initial landing pages
- Being connected to the full stack for the sake of test, including known data starting positions, which up-time requirements
- Having brittle tests because or subtleties, inconsistent timings or #3 specifically
- Depending on Selenium to cover too much of your app, and therefore taking up too much of the total build duration
Dev teams always end up regretting too much Selenium usage.
My Repo for this is https://github.com/paul-hammant/greyangular. It is quite buildable and runnable.
You can try the component pages against real/live services like so:
Note the URL that’s just for this component, and the debugging in the page that Decdnorator will take it out as it stitches the final product.
Clicking “Search”, will go to the separate SearchResults.html component.
Search Criteria params are to the right of the # in the URL. In the Java stack, the actual search is performed in association with this component.
Clicking “Show Schedule”, will go to the separate ShowSchedule.html component.
Show a Schedule
The Schedule for one search results route. A search must have happened first in order to be on the component.
Each of those three components has a TestNG test class. The test class tests positive, error, and exception paths for the component. The TestNG class instantiates WebDriver (in conjunction with FluentSelenium), and opens the page. The page goes off to the real CDNs for Angular, AngularUI and Bootstrap bits and pieces, but uses non-real services via Moco. In other words the tests will still pass if Greyhound.com is down (it was once during my development of this).
Here’s what I’m testing using a browser to cover all the JS + Angular-HTML paths that could go live:
- Search Criteria Component
- date and time interaction changes search model
- clicking search button runs though validations
- origin and destination interaction changes search model
- Search Results Component
- bogus params forces a return to criteria page
- missing params forces a return to criteria page
- no search results shows suitable error
- progress bar appears before results
- for search results clicking selection acts as radio button
- Show Schedule Component
- schedule can be shown
- no schedule shows suitable error
The application being built & tested (Video)
Unit, Integration and Component (UI) tests
The whole test suite takes 33 seconds including all the Maven stuff. Five seconds of that is WebDriver opening Firefox (I wish there were a way to have the pre-opened). Anyway, 33 secs as I say:
mvn clean install
The Java unit tests take 5 seconds, but there is not that many of them. Similarly integration - 10 secs, and component (via Selenium/WebDriver) - 21 secs.
You could run those like so, if you wanted:
mvn clean install -PunitTests mvn clean install -PintegrationTests mvn clean install -PuiTests # all three of those: mvn clean install -PallTests
The final composed application (Video)
mvn clean jetty:run-war -Psites
I’m making two “final” sites. One that uses the accordion control from the Angular-UI Bootstrap library (search results below search criteria), and one that uses a tab-set (search results to the right of search criteria). Because this is only a proof of concept, I’ve called one index_1.html and the other index_1.html, but they are totally separate.
You would deploy these two as index.html to two different domains (and CDN at that), and link them back to the same servlet instance. Or you’d use Google’s AppEngine to host one each, including the servlet app (Greyhound.com is your REAL backend in this case). I like to think as AppEngine as a CDN with a built-in servlet container.
The two brands:
Accordion (49 secs, incl build)
Play with AngularUI’s accordions here http://angular-ui.github.io/bootstrap (10% of the way down the page)
Tabset (24 secs)
Play with AngularUI’s tabsets here http://angular-ui.github.io/bootstrap (80% of the way down the page)
Full-Stack (UI) tests
Yet more tests with the real Servlet container (no Moco), and hence real Greyhound.com services. That introduces brittleness of course. The upside is that I’ve reused the component tests code. Those two brands via WebDriver tests:
mvn clean install -Psites,fullStackTests
More about the tech solution
A different type of modularity
The take away is that the HTML component pages are designed for testability. Even the attaching of CSS is unnecessary at that stage.
Throwing the baby out with the bathwater?
An expression for those not native in English.
Many will think I have gone too far with this. Here is what I could have done with a more idiomatic solution:
ngView and ngInclude
These are dev team recommended way of modularizing Angular HTML fragments. They allow such HTML code to be shared into multiple locations in an app, and separated for coding/testing purposes (Jasmine and Karma normally). I’m using Decdnorator as a poor approximation of this. My downside is that HTML fragments may be shipped to the browser more than once, though many would claim its not going to be a problem if things are inline and cashable.
In my app, I made two “single page apps” (SPAs) for two different brands. A third brand could be more like the page-separated components that were tested with WebDriver. They’d be smaller, and it would be possible to retain all the state management and back/forward button processing we want (yes yes, mine has bugs in that regard at time of publication).
Angular’s $routes and $location
As well as mapping
http://example.com/resource/#/routeXyz to separate views, Angular’s $routes & $location services allow for the URL to change without the page changing. At least much more smoothly that I have it now. This should be amongst the first things a team would put back in versus the regrettable usage of
window.location.href as it is now. If nothing else, a more sophisticated application is going to need such things sooner or later.
Services, Directives and Modules per se
Scopes, $apply and $watch
Because I’m stitching a single controller, I only really have one scope. Actually HTML form usage makes another small scope (and I’d rather it did not). I’m not using $apply anywhere, and I’m not using $watch on any model object. More about these in another blog entry. Of course Angular-UI could be using both of those (and nested scopes) under the hood, but that is none of my concern.
Jasmine, Karma or Protractor
These are from the Angular team. Karma used to be called Testacular, and the team had huge critical acclaim with JsTestDriver before that. I’m not using any of these. I expect the greatest amount of criticism for the implicit proposal I’m making from that choice.
As Agile enthusiasts we’ve been keen on Test Driven Development (TDD) for the longest time. And from that, with good mocking techniques, we claim to be able to move fast, refactor casually, and arrive and “most stable” for the application soonest, and stay there as we expand functionality over a series of iterations. Then there is the “Test Pyramid” (Martin’s Article) that suggests we should have a lot of unit tests, less integration tests and less still functional tests. Without all that good agile balance, we’re heading for trouble, especially so if we’re too focussed on design up front (or have no design in mind at all) (Martin’s ‘Design Stamina Hypothesis’ Article).
I don’t think there is no design, or too much design up front here. Really, all I set out with was 1. Component Pages, 2. Component tests with Java/Selenium, and 3) stitch bits together for a finished site. I knew I’d have to get inventive so that components cooperated on one bus.
Angular normally uses $error property scotch-taped to model objects. I don’t really like it so I’m not using it. Instead I have another item rooted in $scope called errors. I have to manually reset on each revalidation.
Yet more about the development experience
Moco was fun to use, but I wish it were a little more like Mockito. One thing that’s probably insurmountable is the fact that the server (Moco’s HTTP handler) and the client (Java invocation of WebDriver via FluentSelenium) are on different threads, while under TestNG control. It would be the same for JUnit. The issue is that you want to fail on the server side, if a parameter came back that was unexpected. You can’t do a assertion error in the thread that’s responding to incoming HTTP threads, as it is not going to get reported back to TestNG as a failure - it’s going to get consumed, or at best passed to System.err. Thus, I’m doing a StringBuilder trick to capture params, then verify them back in the main thread (that’s under TestNG’s control).
TestNG and Guice injected things
TestNG (a first time for me) is interesting. JUnit has always felt static, and I’ve a problem with that generally given a history with Inversion of Control and Dependency Injection going back 14 and 10 years respectively. TestNG is more instance centric in terms of the runner, and allows advanced things like Dependency Injection. I’m using that to pass in a single Window of Firefox under WebDriver/FluentSelenium control. All test methods share that - yay! The trouble is that I found myself using a ModuleFactory (a Guice-TestNG thing), and the runner instantiated that N times. Yeesh - I only wanted one Guice Module making one WebDriver for all tests. So, I’m keeping the Module as a static variable and making it is only made once with a shitty guard:
- Kate Moczydlowski for helping experiment with Moco and borrowing Greyhound.com services.
- Brandon Byers for a proof read and comments - particularly re Jasmine and design for testability.
blog comments powered by Disqus