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.
Who to Blame – Test Code or Source Code?
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?
Confused, I re-read my source code.
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:
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:
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
Keep Calm and Be Asynchronous
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
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:
So I’ll need to add a callback function to my
Now everything works!
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.
- 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
About 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.