Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

Spying on AngularJS Services That Are called Directly as Functions

english mobile

Jasmine spies are great. But when testing AngularJS services, it can really be be hard to test that the correct calls are being made to services where you’re calling the service itself instead of an attached method, such as the $interval service. When you’re testing controllers, you can directly inject your own spy if you want to test that $interval was called with the correct arguments, but how do you get hold of a function that’s not attached to an object to be able to spy on it?
It turns out that your only option is to replace the service with your spy in the config phase with either $provide.decorator, or module.decorator. For your TL;DR convenience, here is a working module that you can add to the list of modules you instantiate before each test. I’ll explain why all this works after the code.

angular.module('spies.interval', [])
    .decorator('$interval', function($delegate){
       var spy = jasmine.createSpy(‘$interval’).and.callFake(
           function()  {
               return  $delegate.apply(arguments);
           }
       )
       spy.cancel =  angular.bind($delegate, $delegate.cancel);
       spy.flush =  angular.bind($delegate, $delegate.flush);
       spyOn(spy,  'cancel').and.callThrough();
       return spy;
    })

The spy game

Let’s first think about what happens when you call spyOn(someObj, ‘someFunction’), so we can understand why we’re not using that syntax here. Jasmine looks at someObj. If someObj has a function called ‘someFunction’, jasmine overwrites that function with a generic spy which may or may not call the original someFunction. But without someObj, there is nothing to attach that replacement function to to make the replacement invisible to the calling code under test.

Knowing when to delegate

So we have to “trick” Angular into injecting our replacement into the code under test when the code asks for the $interval service. We do this by returning the actual spy rather than an altered $delegate.
This then becomes very different from injecting a fake $interval service into a controller that’s created for just one test, because when you call $interval(), it caches the method to call after the elapsed time, the elapsed time, and so forth internally so that it can work its magic. So if you only replace $interval with a spy, you can no longer test that the right things happen after the time has elapsed, because that internal state isn’t right.
So we need to go on to call the actual $interval service, and we need to call it with the arguments that our service under test is using, which is why we use apply(). Once we’ve done that, we need to make sure to return the result of the call, a promise, so that when we test that code that should be canceled in our client code does not run, that doesn’t break because there’s no promise to give cancel().
Once you’ve taken all of the above into account, you have
var spy = jasmine.createSpy(‘$interval’).and.callFake(
    function() {
        return $delegate.apply(arguments);
})

Spies upon spies

 But you can’t call cancel yet (or flush), because the spy you’re replacing $interval with doesn’t have a cancel() method or a flush() method. If you try just directly adding the functions (spy.cancel = $delegate.cancel), the functions don’t run in the same scope that we wrote to when we called $delegate.apply().  By using angular.bind, we force them into the same scope as the delegate like so:
spy.cancel = angular.bind($delegate, $delegate.cancel);
spy.flush = angular.bind($delegate, $delegate.flush);
Finally, we spy on the cancel function. This is just a convenience—you can of course spy on this function as normal from any test.
spyOn(spy, 'cancel').and.callThrough();
And that, folks, is how you create a spy to be able to check that an angular service where the function is called directly was called with the correct methods without breaking the service.

But wait, there’s more

If you’re a Jasmine expert, you’re probably thinking that I could have just used the undocumented second argument of createSpy, which simplifies the code to this:
angular.module('spies.interval', [])
     .decorator('$interval', function($delegate){
        var spy =  jasmine.createSpy('$interval', $delegate);
        spyOn(spy,  'cancel').and.callThrough();
        return spy;    
})
Unfortunately, that doesn’t work. You would expect the Strategy to do the same thing internally that my home-rolled version does, but it doesn’t. Maybe some other day I’ll dig through the code and blog about why.

How useful is this anyway?

I think the main reason to use this is when the amount of time you need to wait or the optional arguments at the end will vary based on some data. However, your test usually can’t even see the method that ultimately gets called by $interval. So it feels a bit like it should not need to know so much about it that it is looking at what arguments are being passed to this function it can’t see.
So after all this, I ultimately came to feel that though you can do this, you probably shouldn’t, because it introduces a lot of coupling between your test and the internals of the methods in your service that you’re trying to test. For example, your test should not need to know whether your service internally creates its own closure to store state to call the method from in $interval or whether it directly stores that state in the end parameters of $interval.
In Scotland, they have a saying that a gentleman is a man who knows how to play the bagpipes, but doesn’t. I often feel that programming is like that.

What's new in Flash Builder 4.5 for Test Driven Development

english mobile

I love Test Driven Development (TDD), but there are some parts of it that I've found pretty painful in the released versions of Flash/Flex Builder. Given the benefits of TDD, I've chosen to grit my teeth and just bear with it. Last month I went to 360Flex, which showcased some of the productivity enhancements in Flash Builder 4.5, and I am thrilled to see that Adobe has addressed these pain points.

Automatic Class Generation

In most languages, TDD consists of writing a test, running it and watching it fail, writing code, and running it and (ideally) watching it pass. If it doesn't, keep modifying the code until it passes. In compiled languages such as Actionscript, many coders will add a step in the beginning where they write the first test before the Class under test exists. I have found this a pain in Flex 3 and Flash Builder 4, because my workflow winds up something like writing the test, knowing it won't even compile, then copying the name of the new Class. Then I'd use the New Class dialogue to create the Class, making sure to paste in the name of the Class from my Test.

Finally, I'd add the import statement so that mxmlc could compile the new Class into my test.

Flex 4.5 adds a new problem indicator that shows that something

is missing, a white question mark in an orange circle. When you see this, it means that there is a reference on that line that Flex doesn't yet know how to interpret. You'll be able to figure out exactly what by looking for the squiggly red line similar to what you see in a word processor when you've mistyped a word.

If you highlight the word and press Control+1 (Command+1 for Mac users), Flex will offer you the option of creating the missing definition. So you can create the Class definition right from the reference, no copying and pasting needed. Most of the time, Flex will even add the import statement as well.

Automatic Member Generation

By the same token, when you create a new property or method on your new Class, you can generate it the same way, by highlighting it and pressint Ctrl+1. Note that if the Class is inside a Library project, you'll still have to create the member variables or properties by hand, as this doesn't seem to work across projects as of this writing.

Refactoring Help

A lot of times I get halfway through a project and I realize that the Class really should be in a different package. One thing I hate about TDD is that it means there are way more references to a Class under test than one that is not tested. This means that you have to go in and fix the references to the Class you moved, not just in your project source code, but in all of your tests. In effect, your tests can lock you into a less than optimal directory structure.

Flash Builder 4.5 has changed all that. Just move or rename your Class, and all the references to it will update—and it won't take forever or crash Flash Builder (looking at you, Flex 3).


Flash Builder 4.5 has a lot of productivity enhancements like this, and you can get a full list here.

Changes for Running Unit Tests

When I first pointed Flash Builder 4.5 to my existing Flex 4.1 project, I couldn't figure out how to run my unit tests, because the "Execute FlexUnit Tests" menu item is now missing from the "Run" menu, and the application FlasnBuilder 4 created for my old unit tests had an error in it so that it wouldn't run. I wound up deleting those mxml files, but then I was at a bit of a loss as to how to run my existing unit tests.

It turns out that you need to right click on your Test Case or Suite and then select either "Run As FlexUnit Tests" or "Execute FlexUnit Tests." The major difference I see between these two options is that "Run As" provides a dialog similar to what you saw in Flash Builder 4 where you can select multiple Test Cases and/or Suites to run at one time. "Execute FlexUnit Tests" seems to run tests within the selected file or folder only, without offering the dialogue that would allow you to run other tests.

Once you have run a unit test from one of these right click menus, it will be available under the "Run" and "Debug" buttons in the main menu, but you can't change which tests will be run when you do this.

All in all, I see the changes in Flash Builder 4.5 as extremely positive for fans of TDD. I would like to see Adobe go a lot further, but overall this new version is a huge step forward.