It took me two weeks to completely finish the image editor project that I have been really proud of, and I thought the following days would be the most enjoyable moment in the development process: writing some tests, watching those shiny green ticks telling me all the tests passed .. but they didn’t.

fail crop

Who to Blame – Test Code or Source Code?

I use the Jasmine jQuery Plugin to test all my JavaScript code. Just by looking at the output, the shiny red crosses tell me the failed tests are expecting some jQuery object to be visible, but they are not. Specifically, all the failed tests relate to the object with selector .drag-to-reposition. Given that I’m pretty sure the object is visible – I see it right there in the browser – I started to wonder if I made a mistake while writing the tests.

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=a.jsWriting tests with this tool couldn’t be more straightforward. The related test case is nothing fancy – I expect something to be either hidden or visible. So why is .drag-to-reposition so special? Alternatively, if the error was in the source code, why could I see the intended result in the browser? image01

Confused, I re-read my source code.

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=b.js

It looks like I got an important clue! On line 14-16, I only show the $dragToReposition label when the array fabricCanvas.getObjects() is not empty, so this explains why that particular test failed. However, this creates a bigger problem – this array is empty, which means my canvas doesn’t have an image in it. However, I can see the image in the canvas, and I can see the “drag to reposition” label in the browser. What gives?

Jasmine and the Initialization Process

Jasmine allows me to run certain code before each test case, so I can do some initialization work for each test to keep them independent.

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=c.jsAt this stage, I should assume that the init method must have been called, and therefore myEditor is tested to be “truthy”, meaning init returns the instance properly. Additionally, the whole block of beforeEach is run before the tests in describe,  but the tests failed. This makes me wonder if there is something wrong with myEditor. It might not be ready to be used by the time the tests are run. Here is a simplified version of my init implementation:

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=d.js

One useful way to debug code is to insert a number of console.log into the code to see how the function is actually executed under the hood.

… and this is what happened:

image03

This result confirmed my guess. I should expect here: inside to be printed out between “just before” and “just after”, but it’s delayed until “init is done”. This means myEditor is the instance returned right after printing “init is done”, but by that time the image is not fully loaded from the URL. This is the reason fabricCanvas.getObjects() returns an empty array –  there is no object in the canvas yet when “init is done”, and tests are run on the incomplete myEditor.

Keep Calm and Be Asynchronous

Notice how fabric.Image.fromURL takes a callback function as the second argument. An asynchronous callback function passed as an argument is only run when its method is fully executed. In this case, the function that prints “here: inside” is only run when fromURL finishes its tasks. Therefore, to solve the problem, we need Jasmine to wait for fromURL in beforeEach before moving onto the tests.

Luckily, Jasmine supports async. If I scroll to the bottom part of the documentation, I see that I can pass a done callback to beforeEach, so that it only moves to the tests when done is called. I would need something like this:

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=e.js

So I’ll need to add a callback function to my init options:

https://gist.github.com/jriecken/04fbfc5486ef22ddf9f8.js?file=f.js

Now everything works!

image00

But why did everything look fine in my browser?

The short answer is humans are much slower than computers. When I load the page in the browser, I did not click on anything right after the tool is loaded (i.e., init is called). Even half a second to move my mouse will allow that image to be loaded before I click on anything. That is why a user would not notice any problems.

So does it matter?

Absolutely. Taking advantage of that delay is definitely a bad practice, and in fact, asynchronous callbacks are designed for this purpose – something is guaranteed to run after some other things, in order. This is essential in automated tests as well as APIs.

Takeaways

  • When writing a function which calls a function that takes an async callback argument, always provide a callback argument for your own function
  • Tests are important. It will catch potential problems that you don’t encounter.
  • Don’t be afraid to modify your source code when you think it’s perfect, and it’s not always a bad idea to do so to adapt tests

Steven XuAbout the Author

Steven Xu is a co-op software engineer on Hootsuite’s Campaigns UX team, going to 3rd year Computer Science at University of Waterloo. When he is not in front of his computer, he likes playing ping pong, jogging, and exploring everything nearby.