Monday, 29 December 2014

Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 1

There's a small open source project I'm responsible for called jQuery Validation Unobtrusive Native. (A catchy name is a must for any good open source project. Alas I'm not quite meeting my own exacting standards on this particular point... I should have gone with my gut and called it "Livingstone" instead. Too late now...)

The project itself is fairly simple in purpose. It's essentially a bridge between ASP.Net MVC's inbuilt support for driving validation from data attributes and jQuery Validation's native support for the same. It is, in the end, a collection of ASP.Net MVC HTML helper extensions. It is not massively complicated.

I believe it was Tony Blair that said "documentation, documentation, documentation" were his priorities for open source projects. Or maybe it was someone else... Anyway, the point stands. Documentation is supremely important if you want your project to be in any way useful to anyone other than yourself. A project, no matter how fantastic, which lacks decent documentation is a missed opportunity.

Anyway I'm happy to say that jQuery Validation Unobtrusive Native has documentation! And pretty good documentation at that. The documentation takes the form of the jVUNDemo project which is part of the jQuery Validation Unobtrusive Native repo. jVUNDemo is an ASP.Net MVC web application which is built on top of the jQuery Validation Unobtrusive Native helpers. It demonstrates the helpers in action and documents how you might go about using them. It looks a bit like this:

When I first put jVUNDemo together I hosted it on Azure so the world could see it in all it's finery. And that worked just fine. However, there's something you ought to know about me:

I'm quite cheap

No really, I am. My attention was grabbed by the essentially "free" nature of GitHub Pages. I was immediately seized by the desire to somehow deploy jVUNDemo to GitHub Pages and roll around joyfully in all that lovely free hosting.

"But", I hear you cry, "you can't deploy an ASP.Net MVC application to Git Hub Pages... It only hosts static sites!" Quite right. Sort of. This is where I get to pull my ace of spades: jVUNDemo is not really an "app" so much as a static site. Yup, once the HTML that makes up each page is generated there isn't any app like business to do. It's just a collection of text and 1 screen demo's really.

That being the case, there's no reason why I shouldn't be able to make use of GitHub Pages. So that's what I decided to do. Whilst I was at it I also wanted to automate the deployment process. When I tweak jVUNDemo I don't want to have to manually push out a new version of jVUNDemo to wherever it's being hosted. No, I'm a developer so I'll automate it.

I've broken this up into 2 posts. This first one will cover how you generate a static site from an ASP.Net MVC site. The second will be about how to use AppVeyor to ensure that on each build your documentation is getting republished.

You Wget me?

So, static site generation. There's a well known Unix utility called Wget which covers exactly that ground and so we're going to use it. It downloads and saves HTML, it recursively walks the links inside the site and grabs those pages too and it converts our routes so they are locally browsable ("Demo/Required" becomes "Demo/Required.html").

You can use Chocolatey to get a copy of Wget. We're also going to need IIS Express to host jVUNDemo whilst Wget converts it. Once jVUNDemo has been built successfully you should be be able to kick off the process like so:


cd C:\projects\jquery-validation-unobtrusive-native
.\makeStatic.ps1 $pwd.path

This invokes the makeStatic Powershell script in the root of the jQuery Validation Unobtrusive Native repo:


param([string]$buildFolder)

$jVUNDemo = "$($buildFolder)\jVUNDemo"
$staticSiteParentPath = (get-item $buildFolder).Parent.FullName
$staticSite = "static-site"
$staticSitePath = "$($staticSiteParentPath)\$($staticSite)"
$wgetLogPath = "$($staticSiteParentPath)\wget.log"
$port = 57612
$servedAt = "http://localhost:$($port)/"
write-host "jVUNDemo location: $jVUNDemo"
write-host "static site parent location: $staticSiteParentPath"
write-host "static site location: $staticSitePath"
write-host "wget log path: $wgetLogPath"

write-host "Spin up jVUNDemo site at $($servedAt)"
$process = Start-Process 'C:\Program Files (x86)\IIS Express\iisexpress.exe' -NoNewWindow -ArgumentList "/path:$($jVUNDemo) /port:$($port)"

write-host "Wait a moment for IIS to startup"
Start-sleep -s 15

if (Test-Path $staticSitePath) { 
    write-host "Removing $($staticSitePath)..."
    Remove-Item -path $staticSitePath -Recurse -Force
}

write-host "Create static version of demo site here: $($staticSitePath)"
Push-Location $staticSiteParentPath
# 2>&1 used to combine stderr and stdout
wget.exe --recursive --convert-links -E --directory-prefix=$staticSite --no-host-directories $servedAt > $wgetLogPath 2>&1
write-host "lastExitCode: $($lastExitCode)"
cat $wgetLogPath
Pop-Location

write-host "Shut down jVUNDemo site"
stop-process -Name iisexpress

if (Test-Path $staticSitePath) { 
    write-host "Contents of $($staticSitePath)"
    ls $staticSitePath
}

The above Powershell does the following:

  1. Starts up IIS Express serving jVUNDemo on http://localhost:57612/
  2. Waits 15 seconds for IIS Express to get itself together (probably a shorter wait time would be sufficient)
  3. Points Wget at jVUNDemo and bellows "go!!!!"
  4. Wget downloads and saves the static version of jVUNDemo to a directory called "static-site"
  5. Stops IIS Express
  6. Prints out the contents of the "static-site" directory

When run, it pumps something like this out:


jVUNDemo location: C:\projects\jquery-validation-unobtrusive-native\jVUNDemo 
static site parent location: C:\projects 
static site location: C:\projects\static-site 
wget log path: C:\projects\wget.log 
Spin up jVUNDemo site at http://localhost:57612/ 
Wait a moment for IIS to startup 
Create static version of demo site here: C:\projects\static-site 
wget.exe : --2014-12-29 07:49:56--  http://localhost:57612/
Resolving localhost... 
127.0.0.1
Connecting to localhost|127.0.0.1|:57612... connected.
HTTP request sent, awaiting response... 
200 OK

..... lots of HTTP requests.....

Downloaded: 30 files, 1.0M in 0.09s (10.8 MB/s)
Converting static-site/Demo/CreditCard.html... 34-0
Converting static-site/Demo/Number.html... 34-0
Converting static-site/Demo/Range.html... 34-0
Converting static-site/Demo/Email.html... 34-0
Converting static-site/AdvancedDemo/CustomValidation.html... 35-0
Converting static-site/Demo/Date.html... 36-0
Converting static-site/Home/License.html... 27-0
Converting static-site/index.html... 29-0
Converting static-site/AdvancedDemo/Dynamic.html... 35-0
Converting static-site/Demo/MaxLengthMinLength.html... 34-0
Converting static-site/Demo/Required.html... 34-0
Converting static-site/AdvancedDemo.html... 32-0
Converting static-site/Demo/Remote.html... 35-0
Converting static-site/Demo/EqualTo.html... 34-0
Converting static-site/AdvancedDemo/Globalize.html... 38-0
Converting static-site/Demo/Url.html... 34-0
Converting static-site/Demo.html... 37-0
Converting static-site/Home/GettingStarted.html... 29-0
Converting static-site/Home/Download.html... 27-0
Converting static-site/AdvancedDemo/Tooltip.html... 34-0
Converted 20 files in 0.03 seconds.
 
Shut down jVUNDemo site 
Contents of C:\projects\static-site 
 
 
    Directory: C:\projects\static-site
 
 
Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        12/29/2014   7:50 AM            AdvancedDemo
d----        12/29/2014   7:50 AM            Content
d----        12/29/2014   7:50 AM            Demo
d----        12/29/2014   7:50 AM            Home
d----        12/29/2014   7:50 AM            Scripts
-a---        12/29/2014   7:50 AM       5967 AdvancedDemo.html
-a---        12/29/2014   7:50 AM       6802 Demo.html
-a---        12/29/2014   7:47 AM      12862 favicon.ico
-a---        12/29/2014   7:50 AM       8069 index.html

And that's it for part 1 my friends! You now have a static version of the ASP.Net MVC site to dazzle the world with. I should say for the purposes of full disclosure that there are 2 pages in the site which are not entirely "static" friendly. For these 2 pages I've put messages in that are displayed when the page is served up in a static format explaining the limitations. Their full glory can still be experienced by cloning the project and running locally.

Next time we'll take the mechanism detailed above and plug it into AppVeyor for some Continuous Integration happiness.

Friday, 12 December 2014

Gulp, npm, long paths and Visual Studio.... Fight!

How I managed to gulp-angular-templatecache working inside Visual Studio

Every now and then something bites you unexpectedly. After a certain amount of pain, the answer comes to you and you know you want to save others from falling into the same deathtrap.

There I was minding my own business and having a play with a Gulp plugin called gulp-angular-templatecache. If you're not aware of it, it "Concatenates and registers AngularJS templates in the $templateCache". I was planning to use it so that all the views in an Angular app of mine were loaded up-front rather than on demand. (It's a first step in making an "offline-first" version of that particular app.)

I digress already. No sooner had I tapped in:


npm install gulp-angular-templatecache --saveDev

Then I noticed my Visual Studio project was no longer compiling. It was dying a death on build with this error:


ASPNETCOMPILER : error ASPRUNTIME: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.

I was dimly aware that there were issues with the nested node_modules leading to Windows-killing paths. This sounded just like that.... And it was! gulp-angular-templatecache had a dependency on gulp-footer which had a dependency on lodash.assign which had a dependency on lodash._basecreatecallback which had.... You see where I'm going? It seems that the lovely lodash has created the path from hell.

For reasons that aren't particularly clear this kills Visual Studio's build process. This is slightly surprising given that our rogue path is sat in the node_modules directory which isn't part of the project in Visual Studio. That being the case you'd imagine that you could do what you liked there. But no, it seems VS is a delicate flower and we must be careful not to offend. Strange.

It's Workaround Time!

After a great deal of digging I found the answer nestled in the middle of an answer on Stack Overflow. To quote:

If you will add "lodash.bind" module to your project's package.json as dependency it will be installed in one level with gulp and not as gulp's dependency

That's right, I just needed to tap enter this at the root of my project:


npm install lodash.bind --saveDev

And all was sweetness and light once more - no more complaints from VS.

The Future

It looks like lodash are on course to address this issue. So one day this this workaround won't be necessary anymore which is good.

However, the general long path issue concerning node / npm hasn't vanished for Windows users. Given VS 2015 is planning to make Gulp and Grunt 1st class citizens of Visual Studio I'm going to guess that sort of issue is likely to arise again and again for other packages. I'm hoping that means that someone will actually fix the underlying path issues that upset Windows with npm.

It sounds like npm are planning to take some steps which is great. But I can't be alone in having a slightly nagging feeling that Windows isn't quite a first class citizen for node / io / npm yet. I really hope it will become one.

Friday, 5 December 2014

What's in a (Domain) Name?

The observant amongst you may have noticed that this blog has a brand new and shiny domain name! That's right, after happily trading under "icanmakethiswork.blogspot.com" for the longest time it's now "blog.icanmakethiswork.io". Trumpets and fanfare!

Why the change? Well let's break that question down a little. First of all, why change at all? Secondly, why change to blog.icanmakethiswork.io?

Why do things have to change at all?

I mean, weren't we happy? Wasn't it all good? Well quite. For the record, I have no complaints of Blogger who have hosted my blog since it began. They've provided good tools and a good service and I'm happy with them.

That said, I've been toying with the idea for a while now of trying out a few other blogging solutions - possibly even hosting it myself. Whilst my plans are far from definite at the moment I'm aware that I don't own icanmakethiswork.blogspot.com - I can't take it with me. So if I want to make a move to change my blogging solution a first step is establishing my own domain name for my blog. I've done that now. If and when I up sticks, people will hopefully come with me as the URL for my blog should not change.

Also, in the back of my mind I'm aware that Google owns Blogger. Given their recent spate of closing services it's certainly possible that the Google reaper could one day call for Blogger. So it makes sense to be ready to move my blog elsewhere should that day come.

Why blog.icanmakethiswork.io?

Why indeed? And why the ".io" suffix? Doesn't that just make you a desperate follower of fashion?

Good questions all, and "no, I hope not". My original plans were to use the domain name "icanmakethiswork.com". icanmakethiswork was the name of the blog and it made sense to keep it in the URL. So off I went to register the domain name when to my surprise I discovered this:

My domain is being cybersquatted! I mean.... What??!!!!

I started to wonder "is there another icanmakethiswork out there"? Am I not the one and only? So I checked with DuckDuckGo ("The search engine that doesn't track you.") and look what I found:

A whole screen of me. Just me.

As of June 3rd 2014 someone has been sitting on my blog name. I was actually rather outraged by this. I became even more so as I discovered that there was a mechanism (not free) by which I could try and buy it off the squatter. I could instead be like my life idol Madonna and go to court to get it back. But frankly in this sense I'm more like Rachel Green in Friends; not litigous.

So that's why I went for icanmakethiswork.io instead. Path of least resistance and all that. I'd still like icanmakethiswork.com to be mine but I'm not going to court and I'm not paying the squatter. Maybe one day I'll get it. Who knows?

Either way, from now on this is blog.icanmakethiswork.io - please stick around!

Is anything else going to change?

Not for now, no.

Wednesday, 26 November 2014

Pretending to be someone you're not and the dark pit of despair

(Coded UI, IE 11 and the "runas" problem)

"I'm not angry, I'm just disappointed."

That's kind of how I feel about Coded UI tests. It may well be that you've never heard of them - in my experience very few people seem to be aware of them. What are they? Well, I've never used Selenium but as best I understand Coded UI is Microsoft's own version of that. Namely it's a way to automate testing, in my case browser-based testing. You can write a suite of tests that will spin up your application and test it out, going from screen to screen, URL to URL and asserting all is as you would expect.

The project that I'm currently working on has a pretty comprehensive set of tests covering the use of the application. Each night as the clock strikes midnight a lonely computer in the West End of London whirrs into life and runs the full suite. It takes about 8 hours and at the end a report slips into your inbox letting you know of any failures.

Sounds brilliant right? How could someone not love this?

Well a number of reasons. First of all, it takes 8 hours!!!! That's a long time; I'd rather learn what I broke today rather than tomorrow.

Also, and this is probably more significant, Coded UI tests are pretty flaky. Let me qualify that. For a test to be particularly useful it has to be quick, repeatable and reliable. As I've said, Coded UI tests are not quick.

By their very nature integration tests (of which Coded UI tests are a type) can never be entirely reliably repeatable. They test your app in it's entirety. So, for example, if a 3rd party service goes down for 5 minutes then you will get failed tests. You'll burn time investigating these false positives.

Further to that, Coded UI tests are repeatable, except when they're not. I've seen colleagues reduced to near tears by incredible sensitivity of Coded UI tests. Out of the box Coded UI tests appear to ship with the "Works on my machine" guarantee. It requires far more effort that you'd expect to come up with tests that can be reliably expected to pass. They will fail for surprising reasons. For instance, did you know that using the 2.x branch of jQuery won't work with Coded UI? Neither did I. I've lost track of the time that has been wasted running the same test in multiple different environments trying to identify what exactly is upsetting Coded UI about the environment this time.

It is sad but true that with Coded UI tests you can spend an enormous amount of time maintaining the test pack on a day to day basis. As infrastructure and project dependencies are upgraded you will sadly discover Coded UI has once again gone into the foetal position and has to tempted back to normal functioning by whispering sweet nothings in it's ear. ("It's not true that they've ended support for Windows XP" / "IE 6 will live forever" and so on)

Coded UI also appears to be badly supported by Microsoft. Documentation is pretty sparse and, as we'll come back to in a minute, Coded UI is sometimes broken or damaged by other products shipped by Microsoft. This makes it hard to have faith in Coded UI. Indeed, if you're thinking of automating your QA testing my advice would be "look into Selenium". Not because I've used it (I haven't) but those I've met who have used Selenium and Coded UI say Selenium wins hands down.

And yet, and yet...

All of the above said, if you have a Coded UI test suite it can still pay dividends. Significant dividends. As I mentioned, my current project has a significant coverage of Coded UI tests. We've crawled over a lot of broken glass to put these together. But now they're there it is undeniably useful.

Every now and then we'll do a significant refactor of part of the application. For instance, we've entirely changed our persistence strategy in the app but been able to check the code in with a high degree of confidence gleaned from running our test suite using the refactored codebase.

Let me be clear: Coded UI tests can be useful.

The "runas" Problem

Long preamble over, this post is about how to work around the latest issue Coded UI has thrown in our direction. I call it the "runas" problem. Our application is a Knockout / ASP.Net MVC web app built to be used in an intranet environment. By that I mean that identity is handled by Active Directory / Windows Authentication. When someone logs into our app we know who they are without them having to directly supply us with a username and password. No, by logging into their computer they have announced just who they are and Internet Explorer (for it is he) will pass along the credentials. (The app can be used with pretty much any browser but we're only mandated to support IE 9+.)

In order that we can test the app we have a number of test accounts set up in Active Directory. These test accounts have been assigned various roles (viewer / editor / administrator etc). Our tests are designed to run using these accounts in order that all scenarios can be adequately tested.

To achieve this lofty goal the following code (or something very like it) is executed as the first step in any Coded UI test:


string browserLocation = "C:\\Program Files\\Internet Explorer\\iexplore.exe";
string url = "http://localhost:12345/";
string username = "test.editor";
string domain = "theDomain";
var password = new SecureString();
foreach (char c in "test.editor.password")
{
    password.AppendChar(c);
}

ApplicationUnderTest.Launch(browserLocation, null, url, username, password, domain);

What this does is fire up Internet Explorer as the supplied user of theDomain\test.editor, and navigate to the home page. With that as our starting place we could dependably then run a test as this test user. This was a solution not without quirks (on occasion Coded UI tests would "stutter" - repeating each keypress 3 times with calamitous effects). But generally, this worked.

Until that is either Visual Studio 2013 Update 3 or Internet Explorer 11 was installed. One of these (and it appears to be hotly contested) broke the ability to run the above code successfully. After these were installed running the above code resulted in the following error message:

"The application cannot be started. This could be due to one of the following reasons:

  1. Another instance of the application is already running and only one instance can be running at a time.
  2. The application started another process and has now stopped. You may need to launch the process directly.
  3. You do not have sufficient privileges for this application." File: C:\Program Files\Internet Explorer\iexplore.exe."

Lamentably, this was pretty much unresolvable and logging it with Microsoft yielded nothing helpful. This is what I mean about Coded UI being badly supported by Microsoft. Despite my best efforts to report this issue both to Connect and elsewhere and in the end nothing useful happened.

So what to do? I still have Coded UI tests, I still need to be able to run them. And crucially I need to be able to run them impersonating a different user. What to do indeed....

The hack workaround

After IE 11 / Visual Studio Update 3 / whatev's was installed I was left with a setup that allowed me to run Coded UI tests, but only as the current user. On that basis I started looking into a little MVC jiggery pokery. All my controllers inherit from a single base controller. Inside there I placed the following extra override:


public abstract class BaseController : System.Web.Mvc.Controller
{
  //...

  protected override void OnAuthorization(AuthorizationContext filterContext)
  {
#if DEBUG
    if (filterContext.HttpContext.IsDebuggingEnabled)// Is compilation debug="true" set in the web.config?
    {
      var userToImpersonate = Session["UserToImpersonate"] as string;
      if (!string.IsNullOrEmpty(userToImpersonate))
      {
        // userToImpersonate example: "test.editor@theDomain.com"
        filterContext.HttpContext.User = new RolePrincipal(new WindowsIdentity(userToImpersonate));
      }
    }
#endif
      base.OnAuthorization(filterContext);
  }

  //...
}

Each request will trigger this method as one of the first steps in the MVC pipeline. What it does is checks the Session for a user to impersonate. (Yes I'm as wary of Session as the next chap - but in this case it's the right tool for the job.) If a user has been specified then it replaces the current user with the Session user. From this point forwards the app is effectively running as that user. That's great!

In order that Coded UI can make use of this mechanism we need to introduce a "hook". This is going to look a bit hacky - bear with me. Inside Global.asax.cs we're going to add a Session_Start method:


protected void Session_Start(object sender, EventArgs eventArgs)
{
#if DEBUG
    // If a user to impersonate has been supplied then add this user to the session
    // Impersonation will happen in the OnAuthorization method of our base MVC controller
    // Note, this is only allowed in debug mode - not in release mode
    // This exists purely to support coded ui tests
    if (Context.IsDebuggingEnabled)  // Is compilation debug="true" set in the web.config?
    {
        var userToImpersonate = Request.QueryString["UserToImpersonate"] as string;
        if (!string.IsNullOrEmpty(userToImpersonate))
        {
            Session.Add("UserToImpersonate", userToImpersonate);
        }
    }
#endif
}

For the first Request in a Session this checks the QueryString for a parameter called UserToImpersonate. If it's found then it's placed into Session. With this hook exposed we can now amend the first step that all our Coded UI tests follow:


// Various lines commented out as doesn't work with IE 11 - left as an example of how it could be done in the past
//string browserLocation = "C:\\Program Files\\Internet Explorer\\iexplore.exe";
string url = "http://localhost:12345/";
string username = "test.editor";
string domain = "theDomain.com";
//var password = new SecureString();
//foreach (char c in "test.editor.password")
//{
//    password.AppendChar(c);
//}

//ApplicationUnderTest.Launch(browserLocation, null, url, username, password, domain);

// Suffixing url with UrlToImpersonate which will be picked up in Session_Start and used to impersonate
// in OnAuthorization in BaseController.  Also no longer using ApplicationUnderTest.Launch; switched to 
// BrowserWindow.Launch
// No longer used parameters: browserLocation, password
var userToImpersonate = username + "@" + domain; // eg "test.editor@theDomain.com"
var urlWithUser = url + "?UserToImpersonate=" + HttpUtility.UrlEncode(userToImpersonate);
var browser = BrowserWindow.Launch(urlWithUser, "-nomerge"); // "-nomerge" flag forces a new session

As you can see we actually need less when we're using this approach. We no longer need to directly specify the password or the browser location. And the user to impersonate is now passed in as the part of the initial URL used to launch the test.

Pay careful attention to the "-nomerge" flag that is passed in. This ensures that when another browser instance is opened a new session will be started. This is essential for "multi-user" tests that run tests for different users as part of the same test. It ensures that "test.editor" and "test.different.editor" can co-exist happily.

What do I think of the workaround?

This approach works reliably and dependably. More so than the original approach which on occasion wouldn't work or would "stutter" keypresses. That's the good news.

The not so good news is that this approach is, in my view, a bit of hack. I want you to know that this isn't my ideal.

I really don't like having to change the actual system code to facilitate the impersonation requirement. Naturally we only ship the release and not the debug builds to Production so the "back door" that this approach provides will not exist in our Production builds. It will only be accessible in our development environments and on our Coded UI test server. But it feels oh so wrong that there is an effective potential back door in the system now. Well, only if the stars were to align in a really terrible (and admittedly rather unlikely) way. But still, you take my point. Caveat emptor and all that. This is something of a cutdown example to illustrate the point. If anyone else intends to use this then I'd suggest doing more to safeguard your approach. Implementing impersonation whitelists so "any" user cannot be impersonated would be a sensible precaution to start with.

Perhaps this is just one more reason that I'm not that enamoured of Coded UI. Once again it is useful but I've had to compromise more than I'd like to keep it's use. If anyone out there has a better solution I would love to hear from you.

Tuesday, 4 November 2014

Using Gulp in Visual Studio instead of Web Optimization

Update 17/02/2015: I've taken the approach discussed in this post a little further - you can see here

I've used a number of tools to package up JavaScript and CSS in my web apps. Andrew Davey's tremendous Cassette has been really useful. Also good (although less powerful/magical) has been Microsoft's very own Microsoft.AspNet.Web.Optimization that ships with MVC.

I was watching the ASP.NET Community Standup from October 7th, 2014 and learned that the ASP.Net team is not planning to migrate Microsoft.AspNet.Web.Optimization to the next version of ASP.Net. Instead they're looking to make use of JavaScript task runners like Grunt and maybe Gulp. Perhaps you're even dimly aware that they've been taking steps to make these runners more of a first class citizen in Visual Studio, hence the recent release of the new and groovy Task Runner Explorer.

Gulp has been on my radar for a while now as has Grunt. By "on my radar" what I really mean is "Hmmmm, I really need to learn this..... perhaps I could wait until the Betamax vs VHS battles are done? Oh never mind, here we go...".

My understanding is that Grunt and Gulp essentially do the same thing (run tasks in JavaScript) but have different approaches. Grunt is more about configuration, Gulp is more about code. At present Gulp also has a performance advantage as it does less IO than Grunt - though I understand that's due to change in the future. But generally my preference is code over configuration. On that basis I decided that I was going to give Gulp first crack.

Bub bye Web Optimization

I already had a project that used Web Optimization to bundle JavaScript and CSS files. When debugging on my own machine Web Optimization served up the full JavaScript and CSS files. Thanks to the magic of source maps I was able to debug the TypeScript that created the JavaScript files too. Which was nice. When I deployed to production, Web Optimization minified and concatenated the JavaScript and CSS files. This meant I had a single HTTP request for JavaScript and a single HTTP request for CSS. This was also... nooice!

I took a copy of my existing project and created a new repo for it on GitHub. It was very simple in terms of bundling. It had a BundleConfig that created 2 bundles; 1 for JavaScript and 1 for CSS:


using System.Web;
using System.Web.Optimization;

namespace Proverb.Web
{
    public class BundleConfig
    {
        // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
        public static void RegisterBundles(BundleCollection bundles)
        {
            var angularApp = new ScriptBundle("~/angularApp").Include(

                // Vendor Scripts 
                "~/scripts/jquery-{version}.js",
                "~/scripts/angular.js",
                "~/scripts/angular-animate.js",
                "~/scripts/angular-route.js",
                "~/scripts/angular-sanitize.js",
                "~/scripts/angular-ui/ui-bootstrap-tpls.js",

                "~/scripts/toastr.js",
                "~/scripts/moment.js",
                "~/scripts/spin.js",
                "~/scripts/underscore.js",

                // Bootstrapping
                "~/app/app.js",
                "~/app/config.route.js",

                // common Modules
                "~/app/common/common.js",
                "~/app/common/logger.js",
                "~/app/common/spinner.js",

                // common.bootstrap Modules
                "~/app/common/bootstrap/bootstrap.dialog.js"
                );

            // directives
            angularApp.IncludeDirectory("~/app/directives", "*.js", true);

            // services
            angularApp.IncludeDirectory("~/app/services", "*.js", true);

            // controllers
            angularApp.IncludeDirectory("~/app/admin", "*.js", true);
            angularApp.IncludeDirectory("~/app/about", "*.js", true);
            angularApp.IncludeDirectory("~/app/dashboard", "*.js", true);
            angularApp.IncludeDirectory("~/app/layout", "*.js", true);
            angularApp.IncludeDirectory("~/app/sayings", "*.js", true);
            angularApp.IncludeDirectory("~/app/sages", "*.js", true);

            bundles.Add(angularApp);

            bundles.Add(new StyleBundle("~/Content/css").Include(
                "~/content/ie10mobile.css",
                "~/content/bootstrap.css",
                "~/content/font-awesome.css",
                "~/content/toastr.css",
                "~/content/styles.css"
            ));
        }
    }
}

I set myself a task. I wanted to be able to work in *exactly* the way I was working now. But using Gulp instead of Web Optimization. I wanted to lose the BundleConfig above and remove Web Optimization from my application, secure in the knowledge that I had lost nothing. Could it be done? Read on!

Installing Gulp (and Associates)

I fired up Visual Studio and looked for an excuse to use the Task Runner Explorer. The first thing I needed was Gulp. My machine already had Node and NPM installed so I went to the command line to install Gulp globally:


npm install gulp -g

Now to start to plug Gulp into my web project. It was time to make the introductions: Visual Studio meet NPM. At the root of the web project I created a package.json file by executing the following command and accepting all the defaults:


npm init

I wanted to add Gulp as a development dependency of my project: ("Development" because I only need to run tasks at development time. My app has no dependency on Gulp at runtime - at that point it's just about serving up static files.)


npm install gulp --save-dev

This installs gulp local to the project as a development dependency. As a result we now have a "node_modules" folder sat in our root which contains our node packages. Currently, as our package.json reveals, this is only gulp:


  "devDependencies": {
    "gulp": "^3.8.8"
  }

It's time to go to town. Let's install all the packages we're going to need to bundle and minify JavaScript and CSS:


npm install gulp-concat gulp-uglify gulp-rev del path gulp-ignore gulp-asset-manifest gulp-minify-css --save-dev

This installs the packages as dev dependencies (as you've probably guessed) and leaves us with a list of dev dependencies like this:


  "devDependencies": {
    "del": "^0.1.3",
    "gulp": "^3.8.8",
    "gulp-asset-manifest": "0.0.5",
    "gulp-concat": "^2.4.1",
    "gulp-ignore": "^1.2.1",
    "gulp-minify-css": "^0.3.10",
    "gulp-rev": "^1.1.0",
    "gulp-uglify": "^1.0.1",
    "path": "^0.4.9"
  }

Making gulpfile.js

So now I was ready. I had everything I needed to replace my BundleConfig.cs. I created a new file called gulpfile.js in the root of my web project that looked like this:


/// <vs AfterBuild='default' />
var gulp = require("gulp");

// Include Our Plugins
var concat = require("gulp-concat");
var ignore = require("gulp-ignore");
var manifest = require("gulp-asset-manifest");
var minifyCss = require("gulp-minify-css");
var uglify = require("gulp-uglify");
var rev = require("gulp-rev");
var del = require("del");
var path = require("path");

var tsjsmapjsSuffix = ".{ts,js.map,js}";
var excludetsjsmap = "**/*.{ts,js.map}";

var bundleNames = { scripts: "scripts", styles: "styles" };

var filesAndFolders = {

    base: ".",
    buildBaseFolder: "./build/",
    debug: "debug",
    release: "release",
    css: "css",

    // The fonts we want Gulp to process
    fonts: ["./fonts/*.*"],

    // The scripts we want Gulp to process - adapted from BundleConfig
    scripts: [
        // Vendor Scripts 
        "./scripts/angular.js",
        "./scripts/angular-animate.js",
        "./scripts/angular-route.js",
        "./scripts/angular-sanitize.js",
        "./scripts/angular-ui/ui-bootstrap-tpls.js",

        "./scripts/toastr.js",
        "./scripts/moment.js",
        "./scripts/spin.js",
        "./scripts/underscore.js",

        // Bootstrapping
        "./app/app" + tsjsmapjsSuffix,
        "./app/config.route" + tsjsmapjsSuffix,

        // common Modules
        "./app/common/common" + tsjsmapjsSuffix,
        "./app/common/logger" + tsjsmapjsSuffix,
        "./app/common/spinner" + tsjsmapjsSuffix,

        // common.bootstrap Modules
        "./app/common/bootstrap/bootstrap.dialog" + tsjsmapjsSuffix,

        // directives
        "./app/directives/**/*" + tsjsmapjsSuffix,

        // services
        "./app/services/**/*" + tsjsmapjsSuffix,

        // controllers
        "./app/about/**/*" + tsjsmapjsSuffix,
        "./app/admin/**/*" + tsjsmapjsSuffix,
        "./app/dashboard/**/*" + tsjsmapjsSuffix,
        "./app/layout/**/*" + tsjsmapjsSuffix,
        "./app/sages/**/*" + tsjsmapjsSuffix,
        "./app/sayings/**/*" + tsjsmapjsSuffix
    ],

    // The styles we want Gulp to process - adapted from BundleConfig
    styles: [
        "./content/ie10mobile.css",
        "./content/bootstrap.css",
        "./content/font-awesome.css",
        "./content/toastr.css",
        "./content/styles.css"
    ]
};

filesAndFolders.debugFolder = filesAndFolders.buildBaseFolder + "/" + filesAndFolders.debug + "/";
filesAndFolders.releaseFolder = filesAndFolders.buildBaseFolder + "/" + filesAndFolders.release + "/";

/**
 * Create a manifest depending upon the supplied arguments
 * 
 * @param {string} manifestName
 * @param {string} bundleName
 * @param {boolean} includeRelativePath
 * @param {string} pathPrepend
 */
function getManifest(manifestName, bundleName, includeRelativePath, pathPrepend) {

    // Determine filename ("./build/manifest-debug.json" or "./build/manifest-release.json"
    var manifestFile = filesAndFolders.buildBaseFolder + "manifest-" + manifestName + ".json";

    return manifest({
        bundleName: bundleName,
        includeRelativePath: includeRelativePath,
        manifestFile: manifestFile,
        log: true,
        pathPrepend: pathPrepend,
        pathSeparator: "/"
    });
}

// Delete the build folder
gulp.task("clean", function (cb) {
    del([filesAndFolders.buildBaseFolder], cb);
});

// Copy across all files in filesAndFolders.scripts to build/debug
gulp.task("scripts-debug", ["clean"], function () {

    return gulp
        .src(filesAndFolders.scripts, { base: filesAndFolders.base })
        .pipe(gulp.dest(filesAndFolders.debugFolder));
});

// Create a manifest.json for the debug build - this should have lots of script files in
gulp.task("manifest-scripts-debug", ["scripts-debug"], function () {

    return gulp
        .src(filesAndFolders.scripts, { base: filesAndFolders.base })
        .pipe(ignore.exclude("**/*.{ts,js.map}")) // Exclude ts and js.map files from the manifest (as they won't become script tags)
        .pipe(getManifest(filesAndFolders.debug, bundleNames.scripts, true));
});

// Copy across all files in filesAndFolders.styles to build/debug
gulp.task("styles-debug", ["clean"], function () {

    return gulp
        .src(filesAndFolders.styles, { base: filesAndFolders.base })
        .pipe(gulp.dest(filesAndFolders.debugFolder));
});

// Create a manifest.json for the debug build - this should have lots of style files in
gulp.task("manifest-styles-debug", ["styles-debug", "manifest-scripts-debug"], function () {

    return gulp
        .src(filesAndFolders.styles, { base: filesAndFolders.base })
        //.pipe(ignore.exclude("**/*.{ts,js.map}")) // Exclude ts and js.map files from the manifest (as they won't become script tags)
        .pipe(getManifest(filesAndFolders.debug, bundleNames.styles, true));
});

// Concatenate & Minify JS for release into a single file
gulp.task("scripts-release", ["clean"], function () {

    return gulp
        .src(filesAndFolders.scripts)
        .pipe(ignore.exclude("**/*.{ts,js.map}"))        // Exclude ts and js.map files - not needed in release mode
                                                         
        .pipe(concat("app.js"))                          // Make a single file - if you want to see the contents then include the line below                                          
        //.pipe(gulp.dest(releaseFolder))                
                                                         
        .pipe(uglify())                                  // Make the file titchy tiny small
        .pipe(rev())                                     // Suffix a version number to it
        .pipe(gulp.dest(filesAndFolders.releaseFolder)); // Write single versioned file to build/release folder
});

// Create a manifest.json for the release build - this should just have a single file for scripts
gulp.task("manifest-scripts-release", ["scripts-release"], function () {

    return gulp
        .src(filesAndFolders.buildBaseFolder + filesAndFolders.release + "/*.js")
        .pipe(getManifest(filesAndFolders.release, bundleNames.scripts, false));
});

// Copy across all files in filesAndFolders.styles to build/debug
gulp.task("styles-release", ["clean"], function () {

    return gulp
        .src(filesAndFolders.styles)
        .pipe(concat("app.css"))          // Make a single file - if you want to see the contents then include the line below
        //.pipe(gulp.dest(releaseFolder))

        .pipe(minifyCss())                // Make the file titchy tiny small
        .pipe(rev())                      // Suffix a version number to it
        .pipe(gulp.dest(filesAndFolders.releaseFolder + "/" + filesAndFolders.css)); // Write single versioned file to build/release folder
});

// Create a manifest.json for the debug build - this should have a single style files in
gulp.task("manifest-styles-release", ["styles-release", "manifest-scripts-release"], function () {

    return gulp
        .src(filesAndFolders.releaseFolder + "**/*.css")
        .pipe(getManifest(filesAndFolders.release, bundleNames.styles, false, filesAndFolders.css + "/"));
});

// Copy across all fonts in filesAndFolders.fonts to both release and debug locations
gulp.task("fonts", ["clean"], function () {

    return gulp
        .src(filesAndFolders.fonts, { base: filesAndFolders.base })
        .pipe(gulp.dest(filesAndFolders.debugFolder))
        .pipe(gulp.dest(filesAndFolders.releaseFolder));
});

// Default Task
gulp.task("default", [
    "scripts-debug", "manifest-scripts-debug", "styles-debug", "manifest-styles-debug",
    "scripts-release", "manifest-scripts-release", "styles-release", "manifest-styles-release",
    "fonts"
]);

What gulpfile.js does

This file does a number of things each time it is run. First of all it deletes any build folder in the root of the web project so we're ready to build anew. Then it packages up files both for debug and for release mode. For debug it does the following:

  1. It copies the ts, js.map and js files declared in filesAndFolders.scripts to the build/debug folder preserving their original folder structure. (So, for example, app/app.ts, app/app.js.map and app/app.js will all end up at build/debug/app/app.ts, build/debug/app/app.js.map and build/debug/app/app.js respectively.) This is done to allow the continued debugging of the original TypeScript files when running in debug mode.
  2. It copies the css files declared in filesAndFolders.styles to the build/debug folder preserving their original folder structure. (So content/bootstrap.css will end up at build/debug/content/bootstrap.css.)
  3. It creates a build/manifest-debug.json file which contains details of all the script and style files that have been packaged up:
    
    {
      "scripts":[
        "scripts/angular.js",
        "scripts/angular-animate.js",
        "scripts/angular-route.js",
        "scripts/angular-sanitize.js",
        "scripts/angular-ui/ui-bootstrap-tpls.js",
        "scripts/toastr.js",
        "scripts/moment.js",
        "scripts/spin.js",
        "scripts/underscore.js",
        "app/app.js",
        "app/config.route.js",
        "app/common/common.js",
        "app/common/logger.js",
        "app/common/spinner.js",
        "app/common/bootstrap/bootstrap.dialog.js",
        "app/directives/imgPerson.js",
        "app/directives/serverError.js",
        "app/directives/sidebar.js",
        "app/directives/spinner.js",
        "app/directives/waiter.js",
        "app/directives/widgetClose.js",
        "app/directives/widgetHeader.js",
        "app/directives/widgetMinimize.js",
        "app/services/datacontext.js",
        "app/services/repositories.js",
        "app/services/repository.sage.js",
        "app/services/repository.saying.js",
        "app/about/about.js",
        "app/admin/admin.js",
        "app/dashboard/dashboard.js",
        "app/layout/shell.js",
        "app/layout/sidebar.js",
        "app/layout/topnav.js",
        "app/sages/sageDetail.js",
        "app/sages/sageEdit.js",
        "app/sages/sages.js",
        "app/sayings/sayingEdit.js",
        "app/sayings/sayings.js"
      ],
      "styles":[
        "content/ie10mobile.css",
        "content/bootstrap.css",
        "content/font-awesome.css",
        "content/toastr.css",
        "content/styles.css"
      ]
    }
    
    

For release our gulpfile works with the same resources but has a different aim. Namely to minimise the the number of HTTP requests, obfuscate the code and version the files produced to prevent caching issues. To achieve those lofty aims it does the following:

  1. It concatenates together all the js files declared in filesAndFolders.scripts, minifies them and writes them to a single build/release/app-{xxxxx}.js file (where -{xxxxx} represents a version created by gulp-rev).
  2. It concatenates together all the css files declared in filesAndFolders.styles, minifies them and writes them to a single build/release/css/app-{xxxxx}.css file. The file is placed in a css subfolder because of relative paths specified in the CSS file.
  3. It creates a build/manifest-release.json file which contains details of all the script and style files that have been packaged up:
    
    {
      "scripts":["app-95d1e06d.js"],
      "styles":["css/app-1a6256ea.css"]
    }
    
    
    As you can see, the number of files included are reduced down to 2; 1 for JavaScript and 1 for CSS.

Finally, for both the debug and release packages the contents of the fonts folder is copied across wholesale, preserving the original folder structure. This is because the CSS files contain relative references that point to the font files. If I had image files which were referenced by my CSS I'd similarly need to include these in the build process.

Task Runner Explorer gets in on the action

The eagle eyed amongst you will also have noticed a peculiar first line to our gulpfile.js:


/// <vs AfterBuild='default' />

This mysterious comment is actually how the Task Runner Explorer hooks our gulpfile.js into the Visual Studio build process. Our "magic comment" ensures that on the AfterBuild event, Task Runner Explorer runs the default task in our gulpfile.js. The reason we're using the AfterBuild event rather than the BeforeBuild event is because our project contains TypeScript and we need the transpiled JavaScript to be created before we can usefully run our package tasks. If we were using JavaScript alone then that wouldn't be an issue and either build event would do.

How do I use this in my HTML?

Well this is magnificent - we have a gulpfile that builds our debug and release packages. The question now is, how do we use it?

Web Optimization made our lives really easy. Up in my head I had a @Styles.Render("~/Content/css") which pushed out my CSS and down at the foot of the body tag I had a @Scripts.Render("~/angularApp") which pushed out my script tags. Styles and Scripts are server-side utilities. It would be very easy to write equivalent utility classes that, depending on whether we were in debug or not, read the appropriate build/manifest-xxxxxx.json file and served up either debug or release style / script tags.

That would be pretty simple - and for what it's worth simple is good. But today I felt like a challenge. What say server side rendering had been outlawed? A draconian ruling had been passed and all you had to play with was HTML / JavaScript and a server API that served up JSON? What would you do then? (All fantasy I know... But go with me on this - it's a journey.) Or more sensibly, what if you just want to remove some of the work your app is doing server-side to bundle and minify. Just serve up static assets instead. Spend less money in Azure why not?

Before I make all the changes let's review where we were. I had a single MVC view which, in terms of bundles, CSS and JavaScript pretty much looked like this:


<!DOCTYPE html>
<html>
<head>
    <!-- ... -->
    @Styles.Render("~/Content/css")
</head>
<body>
    <!-- ... -->

    @Scripts.Render("~/angularApp")
    <script>
    (function () {
        $.getJSON('@Url.Content("~/Home/StartApp")')
            .done(function (startUpData) {

                var appConfig = $.extend({}, startUpData, {
                    appRoot: '@Url.Content("~/")',
                    remoteServiceRoot: '@Url.Content("~/api/")'
                });

                angularApp.start({
                    thirdPartyLibs: {
                        moment: window.moment,
                        toastr: window.toastr,
                        underscore: window._
                    },
                    appConfig: appConfig
                });
            });
    })();
    </script>
</body>
</html>

This is already more a complicated example than most peoples use cases. Essentially what's happening here is both bundles are written out as part of the HTML and then, once the scripts have loaded the Angular app is bootstrapped with some configuration loaded from the server by a good old jQuery AJAX call.

After reading an article about script loading by the magnificently funny Jake Archibald I felt ready. I cast my MVC view to the four winds and created myself a straight HTML file:


<!DOCTYPE html>
<html>
<head>
    <!-- ... -->
</head>
<body>
    <!-- ... -->

    <script src="Scripts/jquery-2.1.1.min.js"></script>
    <script>
        (function () {

            var appConfig = {};
            var scriptsToLoad;

            /**
             * Handler which fires as each script loads
             */
            function onScriptLoad(event) {

                scriptsToLoad -= 1;

                // Now all the scripts are present start the app
                if (scriptsToLoad === 0) {
                    angularApp.start({
                        thirdPartyLibs: {
                            moment: window.moment,
                            toastr: window.toastr,
                            underscore: window._
                        },
                        appConfig: appConfig
                    });
                }
            }

            // Load startup data from the server
            $.getJSON("api/Startup")
                .done(function (startUpData) {

                    appConfig = startUpData;

                    // Determine the assets folder depending upon whether in debug mode or not
                    var buildFolder = appConfig.appRoot + "build/";
                    var debugOrRelease = (appConfig.inDebug ? "debug" : "release");
                    var manifestFile = buildFolder + "manifest-" + debugOrRelease + ".json";
                    var outputFolder = buildFolder + debugOrRelease + "/";

                    // Load JavaScript and CSS listed in manifest file
                    $.getJSON(manifestFile)
                        .done(function (manifest){

                            manifest.styles.forEach(function (href) {
                                var link = document.createElement("link");

                                link.rel = "stylesheet";
                                link.media = "all";
                                link.href = outputFolder + href;

                                document.head.appendChild(link);
                            });

                            scriptsToLoad = manifest.scripts.length;
                            manifest.scripts.forEach(function (src) {
                                var script = document.createElement("script");

                                script.onload = onScriptLoad;
                                script.src = outputFolder + src;
                                script.async = false;

                                document.head.appendChild(script);
                            });
                        })
                });
        })();
    </script>
</body>
</html>

If you very carefully compare the HTML above the MVC view that it replaces you can see the commonalities. They are doing pretty much the same thing - the only real difference is the bootstrapping API. Previously it was an MVC endpoint at /Home/StartApp. Now it's a Web API endpoint at api/Startup. Here's how it works:

  1. A jQuery AJAX call kicks off a call to load the bootstrapping / app config data. Importantly this data includes whether the app is running in debug or not.
  2. Depending on the isDebug flag the app either loads the build/manifest-debug.json or build/manifest-release.json manifest.
  3. For each CSS file in the styles bundle a link element is created and added to the page.
  4. For each JavaScript file in the scripts bundle a script element is created and added to the page.

It's worth pointing out that this also has a performance edge over Web Optimization as the assets are loaded asynchronously! (Yes I know it says script.async = false but that's not what you think it is... Go read Jake's article!)

To finish off I had to make a few tweaks to my web.config:


    <!-- Allow ASP.Net to serve up JSON files -->
    <system.webServer>
        <staticContent>
            <mimeMap fileExtension=".json" mimeType="application/json"/>
        </staticContent>
    </system.webServer>
    
    <!-- The build folder (and it's child folder "debug") will not be cached.
         When people are debugging they don't want to cache -->
    <location path="build">
        <system.webServer>
            <staticContent>
                <clientCache cacheControlMode="DisableCache"/>
            </staticContent>
        </system.webServer>
    </location>
    
    <!-- The release folder will be cached for a loooooong time
         When you're in Production caching is your friend -->
    <location path="build/release">
        <system.webServer>
            <staticContent>
                <clientCache cacheControlMode="UseMaxAge"/>
            </staticContent>
        </system.webServer>
    </location>

I want to publish, how do I include my assets?

It's time for some csproj trickery. I must say I think I'll be glad to see the back of project files when ASP.Net vNext ships. This is what you need:


  <Target Name="AfterBuild">
    <ItemGroup>
      <!-- what ever is in the build folder should be included in the project -->
      <Content Include="build\**\*.*" />
    </ItemGroup>
  </Target>

What's happening here is that *after* a build Visual Studio considers the complete contents of the build folder to part of the project. It's after the build because the folder will be deleted and reconstructed as part of the build.

Monday, 6 October 2014

Caching and Cache-Busting in AngularJS with HTTP interceptors

Loading On-Demand and Caching

I've written before about my own needs for caching and cache-busting when using RequireJS. Long story short, when I'm loading static resources (scripts / views etc) on demand from the server I want to do a little URL fiddling along the way. I want to do that to cater for these 2 scenarios:

  1. In Development - I want my URLs for static resources to have a unique querystring with each request to ensure that resources are loaded afresh each time. (eg so a GET request URL might look like this: "/app/layout/sidebar.html?v=IAmRandomYesRandomRandomIsWhatIAm58965782")
  2. In Production - I want my URLs for static resources to have a querystring with that is driven by the application version number. This means that static resources can potentially be cached with a given querystring - subsequent requests should result in a 304 status code (indicating “Not Modified”) and local cache should be used. But when a new version of the app is rolled out and the app version is incremented then the querystring will change and resources will be loaded anew. (eg a GET request URL might look like this: "/app/layout/sidebar.html?v=1.0.5389.16180")

Loading Views in AngularJS Using this Approach

I have exactly the same use cases when I'm using AngularJS for views. Out of the box with AngularJS 1.x views are loaded lazily (unlike controllers, services etc). For that reason I want to use the same approach I've outlined above to load my views. Also, I want to prepend my URLs with the root of my application - this allows me to cater for my app being deployed in a virtual folder.

It turns out that's pretty easy thanks to HTTP interceptors. They allow you to step into the pipeline and access and modify requests and responses made by your application. When AngularJS loads a view it's the HTTP service doing the heavy lifting. So to deal with my own use case, I just need to add in an HTTP interceptor that amends the get request. This is handled in the example that follows in the configureHttpProvider function: (The example that follows is TypeScript - though if you just chopped out the interface and the type declarations you'd find this is pretty much idiomatic JavaScript)


interface config {
    appRoot: string;              // eg "/"
    inDebug: boolean;             // eg true or false
    urlCacheBusterSuffix: string; // if in debug this might look like this: "v=1412608547047", 
                                  // if not in debug this might look like this: "v=1.0.5389.16180"
}

function configureHttpProvider() {

    // This is the name of our HTTP interceptor 
    var serviceId = "urlInterceptor"; 

    // We're going to create a service factory which will be our HTTP interceptor
    // It will be injected with a config object which is represented by the config interface above
    app.factory(serviceId, ["$templateCache", "config", 
        function ($templateCache: ng.ITemplateCacheService, config: config) {

        // We're returning an object literal with a single function; the "request" function
        var service = {
            request: request
        };

        return service;

        // Request will be called with a request config object which includes the URL which we will amend
        function request(requestConfig: ng.IRequestConfig) {

            // For the loading of HTML templates we want the appRoot to be prefixed to the path
            // and we want a suffix to either allow caching or prevent caching 
            // (depending on whether in debug mode or not)
            if (requestConfig.method === "GET" && endsWith(requestConfig.url, ".html")) {

                // If this has already been placed into a primed template cache then we should leave the URL as is
                // so that the version in templateCache is served.  If we tweak the URL then it will not be found
                var cachedAlready = $templateCache.get(requestConfig.url);
                if (!cachedAlready) {
                    // THIS IS THE MAGIC!!!!!!!!!!!!!!!

                    requestConfig.url = config.appRoot + requestConfig.url + config.urlCacheBusterSuffix;
                
                    // WE NOW HAVE A URL WHICH IS CACHE-FRIENDLY FOR OUR PURPOSES - REJOICE!!!!!!!!!!!
                }
            }

            return requestConfig;
        }

        // a simple JavaScript string "endswith" implementation
        function endsWith(str: string, suffix: string) {
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }
    }]);

    // This adds our service factory interceptor into the pipeline
    app.config(["$httpProvider", function ($httpProvider: ng.IHttpProvider) {
        $httpProvider.interceptors.push(serviceId);
    }]);
}

This interceptor steps in and amends each ajax request when all the following conditions hold true:

  1. It's a GET request.
  2. It's requesting a file that ends ".html" - a template basically.
  3. The template cache does not already contain the template. I left this out at first and got bitten when I found that the contents of the template cache were being ignored for pre-primed templates. Ugly.

Interesting technique.... How do I apply it?

Isn't it always much more helpful when you can see an example of code in the context of which it is actually used? Course it is! If you want that then take a look at app.ts on GitHub. And if you'd like the naked JavaScript well that's there too.

Friday, 3 October 2014

He tasks me; he heaps me.... I will wreak that MOQ upon him.

Enough with the horrific misquotes - this is about Moq and async (that's my slight justification for robbing Herman Melville).

It's pretty straightforward to use Moq to do async testing thanks to it's marvellous ReturnsAsync method. That means it's really easy to test a class that consumes an async API. Below is an example of a class that does just that: (it so happens that this class is a Web API controller but that's pretty irrelevant to be honest)


namespace Proverb.Web.Controllers
{
    // ISageService included inline for ease of explanation
    public interface ISageService
    {
        Task<int> DeleteAsync(int id);
    }

    public class SageController : ApiController
    {
        ISageService _sageService;

        public SageController(ISageService userService) 
        {
            _sageService = userService;
        }

        public async Task<IHttpActionResult> Delete(int id)
        {
            int deleteCount = await _sageService.DeleteAsync(id);

            if (deleteCount == 0)
                return NotFound();
            else
                return Ok();
        }
   }
}

To mock the _sageService.DeleteAsync method it's as easy as this:


namespace Proverb.Web.Tests.ASPNet.Controllers
{
    [TestClass]
    public class SageControllerTests
    {
        private Mock<ISageService> _sageServiceMock;
        private SageController _controller;

        [TestInitialize]
        public void Initialise()
        {
            _sageServiceMock = new Mock<ISageService>();

            _controller = new SageController(_sageServiceMock.Object);
        }

        [TestMethod]
        public async Task Delete_returns_a_NotFound()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(0); // This makes me *so* happy...

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var notFound = result as NotFoundResult;
            Assert.IsNotNull(notFound);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }

        [TestMethod]
        public async Task Delete_returns_an_Ok()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(1); // I'm still excited now!

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var ok = result as OkResult;
            Assert.IsNotNull(ok);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }
    }
}

But wait.... What if there's like... Nothing?

Nope, I'm not getting into metaphysics. Something more simple. What if the async API you're consuming returns just a Task? Not a Task of int but a simple old humble Task.

So to take our example we're going from this:


    public interface ISageService
    {
        Task<int> DeleteAsync(int id);
    }

To this:


    public interface ISageService
    {
        Task DeleteAsync(int id);
    }

Your initial thought might be "well that's okay, I'll just lop off the ReturnsAsync statements and I'm home free". That's what I thought anyway.... And I was *WRONG*! A moments thought and you realise that there's still a return type - it's just Task now. What you want to do is something like this:


            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(void); // This'll definitely work... Probably

No it won't - void is not a real type and much as you might like it to, this is not going to work.

So right now you're thinking, well Moq probably has my back - it'll have something like ReturnsTask, right? Wrong! It's intentional it turns out - there's a discussion on GitHub about the issue. And in that discussion there's just what we need. We can use Task.Delay or Task.FromResult alongside Moq's good old Returns method and we're home free!

Here's one I made earlier...


namespace Proverb.Web.Controllers
{
    // ISageService again included inline for ease of explanation
    public interface ISageService
    {
        Task DeleteAsync(int id);
    }

    public class SageController : ApiController
    {
        ISageService _sageService;

        public SageController(ISageService userService) 
        {
            _sageService = userService;
        }

        public async Task<IHttpActionResult> Delete(int id)
        {
            await _sageService.DeleteAsync(id);

            return Ok();
        }
   }
}


namespace Proverb.Web.Tests.ASPNet.Controllers
{
    [TestClass]
    public class SageControllerTests
    {
        private Mock<ISageService> _sageServiceMock;
        private SageController _controller;

        readonly Task TaskOfNowt = Task.Delay(0);
        // Or you could use this equally valid but slightly more verbose approach:
        //readonly Task TaskOfNowt = Task.FromResult<object>(null);

        [TestInitialize]
        public void Initialise()
        {
            _sageServiceMock = new Mock<ISageService>();

            _controller = new SageController(_sageServiceMock.Object);
        }

        [TestMethod]
        public async Task Delete_returns_an_Ok()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .Returns(TaskOfNowt); // Feels good doesn't it?

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var ok = result as OkResult;
            Assert.IsNotNull(ok);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }
    }
}

Saturday, 13 September 2014

Journalling the Migration of Jasmine Tests to TypeScript

I previously attempted to migrate my Jasmine tests from JavaScript to TypeScript. The last time I tried it didn't go so well and I bailed. Thank the Lord for source control. But feeling I shouldn't be deterred I decided to have another crack at it.

I did manage it this time... Sort of. Unfortunately there was a problem which I discovered right at the end. An issue with the TypeScript / Visual Studio tooling. So, just to be clear, this is not a blog post of "do this and it will work perfectly". On this occasion there will be some rough edges. This post exists, as much as anything else, as a record of the problems I experienced - I hope it will prove useful. Here we go:

What to Migrate?

I'm going to use one of the test files in my my side project Proverb. It's the tests for an AngularJS controller called sageDetail - I've written about it before. Here it is in all it's JavaScript-y glory:


describe("Proverb.Web -> app-> controllers ->", function () {

    beforeEach(function () {

        module("app");
    });

    describe("sageDetail ->", function () {

        var $rootScope,
            getById_deferred, // deferred used for promises
            $location, $routeParams_stub, common, datacontext, // controller dependencies
            sageDetailController; // the controller

        beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _datacontext_) {

            $rootScope = _$rootScope_;
            $q = _$q_;

            $location = _$location_;
            common = _common_;
            datacontext = _datacontext_;

            $routeParams_stub = { id: "10" };
            getById_deferred = $q.defer();

            spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
            spyOn(common, "activateController").and.callThrough();
            spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
            spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));

            sageDetailController = _$controller_("sageDetail", {
                $location: $location,
                $routeParams: $routeParams_stub,
                common: common,
                datacontext: datacontext
            });


        }));

        describe("on creation ->", function () {

            it("controller should have a title of 'Sage Details'", function () {

                expect(sageDetailController.title).toBe("Sage Details");
            });

            it("controller should have no sage", function () {

                expect(sageDetailController.sage).toBeUndefined();
            });

            it("datacontext.sage.getById should be called", function () {

                expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
            });
        });

        describe("activateController ->", function () {

            var sage_stub;
            beforeEach(function () {
                sage_stub = { name: "John" };
            });

            it("should set sages to be the resolved promise values", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                expect(sageDetailController.sage).toBe(sage_stub);
            });

            it("should log 'Activated Sage Details View' and set title with name", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
                expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
            });
        });

        describe("gotoEdit ->", function () {

            var sage_stub;
            beforeEach(function () {
                sage_stub = { id: 20 };
            });

            it("should set $location.path to edit URL", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                sageDetailController.gotoEdit();

                expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
            });
        });
    });
});

Off we go

Righteo. Let's flip the switch. sageDetail.js you shall go to the ball! One wave of my magic wand and sageDetail.js becomes sageDetail.ts... Alakazam!! Of course we've got to do the fiddling with the csproj file to include the dependent JavaScript files. (I'll be very pleased when ASP.Net vNext ships and I don't have to do this anymore....) So find this:


<TypeScriptCompile Include="app\sages\sageDetail.ts" />

And add this:


<Content Include="app\sages\sageDetail.js">
  <DependentUpon>sageDetail.ts</DependentUpon>
</Content>
<Content Include="app\sages\sageDetail.js.map">
  <DependentUpon>sageDetail.ts</DependentUpon>
</Content>

What next? I've a million red squigglies in my code. It's "could not find symbol" city. Why? Typings! We need typings! So let's begin - I'm needing the Jasmine typings for starters. So let's hit NuGet and it looks like we need this:

Install-Package jasmine.TypeScript.DefinitelyTyped

That did no good at all. Still red squigglies. I'm going to hazard a guess that this is something to do with the fact my JavaScript Unit Test project doesn't contain the various TypeScript artefacts that Visual Studio kindly puts into the web csproj for you. This is because I'm keeping my JavaScript tests in a separate project from the code being tested. Also, the Visual Studio TypeScript tooling seems to work on the assumption that TypeScript will only be used within a web project; not a test project. Well I won't let that hold me back... Time to port the TypeScript artefacts in the web csproj over by hand. I'll take this:


<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />

And I'll also take this


<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

Bingo bango - a difference. I no longer have red squigglies under the Jasmine statements (describe, it etc). But alas, I do everywhere else. One in particular draws my eye...

Could not find symbol '$q'

Once again TypeScript picks up the hidden bugs in my JavaScript:


    $q = _$q_;

That's right it's an implicit global. Quickly fixed:


    var $q = _$q_;

Typings? Where we're going, we need typings...

We need more types. We're going to need the types created by our application; our controllers / services / directives etc. As well that we need the types used in the creation of the app. So the Angular typings etc. Since we're going to need to use reference statements to pull in the types created by our application I might as well use them to pull in the required definition files as well (eg angular.d.ts):


/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />

Now we need to work our way through the "variable 'x' implicitly has an 'any' type" messages. One thing we need to do is to amend our original sageDetails.ts file so that the sageDetailRouteParams interface and SageDetail class are exported from the controllers module. We can't use the types otherwise. Now we can add typings to our file - once finished it looks like this:


/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />
describe("Proverb.Web -> app-> controllers ->", function () {

    beforeEach(function () {

        module("app");
    });

    describe("sageDetail ->", function () {

        var $rootScope: ng.IRootScopeService,
            // deferred used for promises 
            getById_deferred: ng.IDeferred<sage>, 
            // controller dependencies
            $location: ng.ILocationService,
            $routeParams_stub: controllers.sageDetailRouteParams,
            common: common,
            datacontext: datacontext,
            sageDetailController: controllers.SageDetail; // the controller

        beforeEach(inject(function (
            _$controller_: any,
            _$rootScope_: ng.IRootScopeService,
            _$q_: ng.IQService,
            _$location_: ng.ILocationService,
            _common_: common,
            _datacontext_: datacontext) {

            $rootScope = _$rootScope_;
            var $q = _$q_;

            $location = _$location_;
            common = _common_;
            datacontext = _datacontext_;

            $routeParams_stub = { id: "10" };
            getById_deferred = $q.defer();

            spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
            spyOn(common, "activateController").and.callThrough();
            spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
            spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));

            sageDetailController = _$controller_("sageDetail", {
                $location: $location,
                $routeParams: $routeParams_stub,
                common: common,
                datacontext: datacontext
            });


        }));

        describe("on creation ->", function () {

            it("controller should have a title of 'Sage Details'", function () {

                expect(sageDetailController.title).toBe("Sage Details");
            });

            it("controller should have no sage", function () {

                expect(sageDetailController.sage).toBeUndefined();
            });

            it("datacontext.sage.getById should be called", function () {

                expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
            });
        });

        describe("activateController ->", function () {

            var sage_stub: sage;
            beforeEach(function () {
                sage_stub = { name: "John", id: 10, username: "John", email: "john@", dateOfBirth: new Date() };
            });

            it("should set sages to be the resolved promise values", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                expect(sageDetailController.sage).toBe(sage_stub);
            });

            it("should log 'Activated Sage Details View' and set title with name", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
                expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
            });
        });

        describe("gotoEdit ->", function () {

            var sage_stub: sage;
            beforeEach(function () {
                sage_stub = { name: "John", id: 20, username: "John", email: "john@", dateOfBirth: new Date() };
            });

            it("should set $location.path to edit URL", function () {

                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                sageDetailController.gotoEdit();

                expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
            });
        });
    });
});

So That's All Good...

Except it's not. When I run the tests using Chutzpah my sageDetail controller tests aren't found. My spider sense is tingling. This is something to do with the reference statements. They're throwing Chutzpah off. No bother, I can fix that with a quick tweak of the project file:


  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>
    <TypeScriptRemoveComments>True</TypeScriptRemoveComments>
  </PropertyGroup>

The TypeScript compiler will now strip comments; which includes the reference statements. Now my tests are detected *and* they run. Yay!

Who Killed the TypeScript Language Service?

Yup it's dead. Whilst the compilation itself has no issues, take a look at the errors being presented for just one of the files back in the original web project:

It looks like having one TypeScript project in a solution which uses reference comments somehow breaks the implicit referencing behaviour built into Visual Studio for other TypeScript projects in the solution. I can say this with some confidence as if I pull out the reference comments from the top of the test file that we've converted then it's business as usual - the TypeScript Language Service lives once more. I'm sure you can see the problem here though: the TypeScript test file doesn't compile. All rather unsatisfactory.

I suspect that if I added reference comments throughout the web project the TypeScript Language Service would be just fine. But I rather like the implicit referencing functionality so I'm not inclined to do that. After reaching something of a brick wall and thinking I had encountered a bug in the TypeScript Language service I raised an issue on GitHub.

Solutions....

Thanks to the help of Mohamed Hegazy it emerged that the problem was down to missing reference comments in my sageDetail controller tests. One thing I had not considered was the 2 different ways each of my TypeScript projects were working:

  • Proverb.Web uses the Visual Studio implicit referencing functionality. This means that I do not need to use reference comments in the TypeScript files in Proverb.Web.
  • Proverb.Web.JavaScript does *not* uses the implicit referencing functionality. It needs reference comments to resolve references.

The important thing to take away from this (and the thing I had overlooked) was that Proverb.Web.JavaScript uses reference comments to pull in Proverb.Web TypeScript files. Those files have dependencies which are *not* stated using reference comments. So the compiler trips up when it tries to walk the dependency tree - there are no reference comments to be followed! So for example, common.ts has a dependency upon logger.ts. Fixing the TypeScript Language Service involves ensuring that the full dependency list is included in the sageDetail controller tests file, like so:


/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-route.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/toastr/toastr.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/underscore/underscore.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/logger.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repositories.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />
/// <reference path="../../../proverb.web/app/services/repository.saying.ts" />
/// <reference path="../../../proverb.web/app/app.ts" />
/// <reference path="../../../proverb.web/app/config.route.ts" />

With this in place you have a working solution, albeit one that is a little flaky. An alternative solution was suggested by Noel Abrahams which I quote here:

Why not do the following?

  • Compile Proverb.Web with --declarations and the option for combining output into a single file. This should create a Proverb.Web.d.ts in your output directory.
  • In Proverb.Web.Tests.JavaScript add a reference to this file.
  • Right-click Proverb.Web.Tests.JavaScript select "Build Dependencies" > "Project Dependencies" and add a reference to Proverb.Web.

I don't think directly referencing TypeScript source files is a good idea, because it causes the file to be rebuilt every time the dependant project is compiled.

Mohamed rather liked this solution. It looks like some more work is due to be done on the TypeScript tooling to make this less headache-y in future.

Wednesday, 10 September 2014

Unit Testing an Angular Controller with Jasmine

Anyone who reads my blog will know that I have been long in the habit of writing unit tests for my C# code. I'm cool like that. However, it took me a while to get up and running writing unit tests for my JavaScript code. I finally got there using a combination of Jasmine 2.0 and Chutzpah. (Jasmine being my test framework and Chutzpah being my test runner.)

I'm getting properly into the habit of testing my JavaScript. I won't pretend it's been particularly fun but I firmly believe it will end up being useful... That's what I tell myself during the long dark tea-times of the soul anyway.

I have a side project called Proverb. It doesn't do anything in particular - for the most part it's a simple application that displays the collected wise sayings of a team that I used to be part of. There's not much to it - a bit of CRUD, a dashboard. Not much more. Because of the project's simplicity it's ideal to use Proverb's underlying idea when trying out new technologies / frameworks. The best way to learn is to do. So if I want to learn "X", then building Proverb using "X" is a good way to go.

I digress already. I had a version of Proverb built using a combination of AngularJS and TypeScript. I had written the Angular side of Proverb without any tests. Now I was able to write JavaScript tests for my Angular code that's just what I set out to do. It should prove something of a of Code Kata too.

Whilst I'm at it I thought it might prove helpful if I wrote up how I approached writing unit tests for a single Angular controller. So here goes.

What I'm Testing

I have an Angular controller called sagesDetail. It powers this screen:

sagesDetail is a very simple controller. It does these things:

  1. Load the "sage" (think of it as just a "user") and make it available on the controller so it can be bound to the view.
  2. Set the view title.
  3. Log view activation.
  4. Expose a gotoEdit method which, when called, redirects the user to the edit screen.

The controller is written in TypeScript and looks like this:

sagesDetail.ts

module controllers {

    "use strict";

    var controllerId = "sageDetail";

    interface sageDetailRouteParams extends ng.route.IRouteParamsService {
        id: string;
    }

    class SageDetail {

        log: loggerFunction;
        sage: sage;
        title: string;

        static $inject = ["$location", "$routeParams", "common", "datacontext"];
        constructor(
            private $location: ng.ILocationService,
            private $routeParams: sageDetailRouteParams,
            private common: common,
            private datacontext: datacontext
            ) {

            this.sage = undefined;
            this.title = "Sage Details";

            this.log = common.logger.getLogFn(controllerId);

            this.activate();
        }

        // Prototype methods

        activate() {
            var id = parseInt(this.$routeParams.id, 10);
            var dataPromises: ng.IPromise<any>[] = [this.datacontext.sage.getById(id, true).then(data => this.sage = data)];

            this.common.activateController(dataPromises, controllerId, this.title)
                .then(() => {
                    this.log("Activated Sage Details View");
                    this.title = "Sage Details: " + this.sage.name;
                });
        }

        gotoEdit() {
            this.$location.path("/sages/edit/" + this.sage.id);
        }
    }

    angular.module("app").controller(controllerId, SageDetail);
}

When compiled to JavaScript it looks like this:

sageDetail.js

var controllers;
(function (controllers) {
    "use strict";

    var controllerId = "sageDetail";

    var SageDetail = (function () {
        function SageDetail($location, $routeParams, common, datacontext) {
            this.$location = $location;
            this.$routeParams = $routeParams;
            this.common = common;
            this.datacontext = datacontext;
            this.sage = undefined;
            this.title = "Sage Details";

            this.log = common.logger.getLogFn(controllerId);

            this.activate();
        }
        // Prototype methods
        SageDetail.prototype.activate = function () {
            var _this = this;
            var id = parseInt(this.$routeParams.id, 10);
            var dataPromises = [this.datacontext.sage.getById(id, true).then(function (data) {
                    return _this.sage = data;
                })];

            this.common.activateController(dataPromises, controllerId, this.title).then(function () {
                _this.log("Activated Sage Details View");
                _this.title = "Sage Details: " + _this.sage.name;
            });
        };

        SageDetail.prototype.gotoEdit = function () {
            this.$location.path("/sages/edit/" + this.sage.id);
        };
        SageDetail.$inject = ["$location", "$routeParams", "common", "datacontext"];
        return SageDetail;
    })();

    angular.module("app").controller(controllerId, SageDetail);
})(controllers || (controllers = {}));
//# sourceMappingURL=sageDetail.js.map

Now for the Tests

I haven't yet made the move of switching over my Jasmine tests from JavaScript to TypeScript. (It's on my list but there's only so many things you can do at once...) For that reason the tests you'll see here are straight JavaScript. Below you will see the tests for the sageDetail controller.

I have put very comments in the test code to make clear the intent to you, dear reader. Annotated the life out of them. Naturally I wouldn't expect a test to be so heavily annotated in a typical test suite - and you can be sure mine normally aren't!

Jasmine tests for sageDetail.js

describe("Proverb.Web -> app-> controllers ->", function () {

    // Before each test runs we're going to need ourselves an Angular App to test - go fetch!
    beforeEach(function () {

        module("app");  // module is an alias for angular.mock.module
    });

    // Tests for the sageDetail controller
    describe("sageDetail ->", function () {

        // Declare describe-scoped variables 
        var $rootScope, 
            getById_deferred, // deferred used for promises
            $location, $routeParams_stub, common, datacontext, // controller dependencies
            sageDetailController; // the controller

        // Before each test runs set up the controller using inject - an alias for angular.mock.inject
        beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _datacontext_) {

            // Note how each parameter is prefixed and suffixed with "_" - this an Angular nicety
            // which allows you to have variables in your tests with the original reference name.
            // So here we assign the injected parameters to the describe-scoped variables:
            $rootScope = _$rootScope_;
            $q = _$q_;
            $location = _$location_;
            common = _common_;
            datacontext = _datacontext_;

            // Our controller has a dependency on an "id" property passed on the $routeParams
            // We're going to stub this out with a JavaScript object literal
            $routeParams_stub = { id: "10" };

            // Our controller depends on a promise returned from this function: datacontext.sage.getById
            // Well strictly speaking it also uses a promise for activateController but since the activateController
            // promise just wraps the getById promise it will be resolved when the getById promise is.
            // Here we create a deferred representing the getById promise which we can resolve as we need to 
            getById_deferred = $q.defer();

            // set up a spy on datacontext.sage.getById and set it to return the promise of getById_deferred
            // this allows us to #1 detect that getById has been called 
            // and #2 resolve / reject our promise as our test requires using getById_deferred
            spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);

            // set up a spy on common.activateController and set it to call through
            // this allows us to detect that activateController has been called whilst 
            // maintaining existing controller functionality
            spyOn(common, "activateController").and.callThrough();

            // set up spys on common.logger.getLogFn and $location.path so we can detect they have been called
            spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
            spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));

            // create a sageDetail controller and inject the dependencies we have set up
            sageDetailController = _$controller_("sageDetail", {
                $location: $location,
                $routeParams: $routeParams_stub,
                common: common,
                datacontext: datacontext
            });
        }));

        // Tests for the controller state at the point of the sageDetail controller's creation
        // ie before the getById / activateController promises have been resolved 
        // So this tests the constructor (function) and the activate function up to the point
        // of the promise calls
        describe("on creation ->", function () {

            it("controller should have a title of 'Sage Details'", function () {

                // tests this code has executed:
                // this.title = "Sage Details";
                expect(sageDetailController.title).toBe("Sage Details");
            });

            it("controller should have no sage", function () {

                // tests this code has executed:
                // this.sage = undefined;
                expect(sageDetailController.sage).toBeUndefined();
            });

            it("datacontext.sage.getById should be called", function () {

                // tests this code has executed:
                // this.datacontext.sage.getById(id, true)
                expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
            });
        });

        // Tests for the controller state at the point of the resolution of the getById promise
        // ie after the getById / activateController promises have been resolved 
        // So this tests the constructor (function) and the activate function after the point
        // of the promise calls
        describe("activateController ->", function () {

            var sage_stub;
            beforeEach(function () {
                // Create a sage stub which will be used when resolving the getById promise
                sage_stub = { name: "John" };
            });

            it("should set sages to be the resolved promise values", function () {

                // Resolve the getById promise with the sage stub
                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                // tests this code has executed:
                // this.sage = data
                expect(sageDetailController.sage).toBe(sage_stub);
            });

            it("should log 'Activated Sage Details View' and set title with name", function () {

                // Resolve the getById promise with the sage stub
                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                // tests this code has executed:
                // this.log("Activated Sage Details View");
                // this.title = "Sage Details: " + this.sage.name;
                expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
                expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
            });
        });

        // Tests for the gotoEdit function on the controller
        // Note that this will only be called *after* a controller has been created
        // and it depends upon a sage having first been loaded
        describe("gotoEdit ->", function () {

            var sage_stub;
            beforeEach(function () {
                // Create a sage stub which will be used when resolving the getById promise
                sage_stub = { id: 20 };
            });

            it("should set $location.path to edit URL", function () {

                // Resolve the getById promise with the sage stub
                getById_deferred.resolve(sage_stub);
                $rootScope.$digest(); // So Angular processes the resolved promise

                sageDetailController.gotoEdit();

                // tests this code has executed:
                // this.$location.path("/sages/edit/" + this.sage.id);
                expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
            });
        });
    });
});