How to mock a nested method? - javascript

I'm learning more about Jasmine Unit Testing and I've ran into something that I can't figure out. I'm new to both JavaScript and Unit Testing. I've tried to look for examples about nested methods and mocking them, but I'm still unable to have a successful test. I'm making a game with PhaserJS (HTML5 Game Library) and I've written successful tests so far. This is an example of my successful test.
function createGameScreenBorder(gameState) {
var border = gameState.game.add.graphics();
}
This is my test block.
it("create gamescreen background border", function() {
gameState.game = {
add: jasmine.createSpyObj('add', ['graphics'])
};
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
});
Now the above code works, it doesn't do much. What I want is to draw a rectangle which is a method part of the graphics method.
function createGameScreenBorder(gameState) {
var border = gameState.game.add.graphics();
// drawRect: x, y width, length
border.drawRect(0, 0, 0, 0);
}
This is my test block.
it("create gamescreen background border", function() {
gameState.game = {
add: {
graphics: jasmine.createSpyObj('graphics', ['drawRect'])
}
}
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
expect(gameState.game.add.graphics().lineStyle).toHaveBeenCalledWith(0,0,0,0);
});
I want to be able to make sure that drawRect() is called with my parameters, but I am confused as to how to do it.
Thank you!

The gameState.game.add.graphics() returns an object that has a drawRect() method on it.
First you want to check if the gameState.game.add.graphics() was called - this is already done. Then check if the drawRect() was called on the object returned from this method. To do that, set up your spy to return an object that also has a spy on it.
it("create gamescreen background border", function() {
let resultObject = {
drawRect: jasmine.createSpy()
};
gameState.game.add = {
graphics: jasmine.createSpy().and.callFake(() => {
return resultObject;
})
};
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
expect(resultObject.drawRect).toHaveBeenCalledWith(0, 0, 0, 0);
});

Related

simple requestAnimationFrame not working JavaScript

I am currently trying to understand animation using JavaScript and HTML5.
From what I have gathered after researching on the internet, requestAnimationFrame repeatedly calls a method.
I have created a plane, and created a method which will move the plane in a diagonal line. However, it there seems to be no animation.
I'm fairly new to this so it could be just me not quite getting the concept. I don't think it is to do with my browser as I have tried it on both chrome and internet explorer, and they should be up to date as I only installed them a few months ago as this is a new laptop.
Here is my main class, it should include all relevent code:
/*global window, document, alert, Vector, Moth, Matrix, Plane, SceneGraphNode*/
function onLoad() {
var mainCanvas, mainContext, planePosition, plane;
// this function will initialise our variables
function initialiseCanvasContext() {
// Find the canvas element using its id attribute.
mainCanvas = document.getElementById('mainCanvas');
// if it couldn't be found
if (!mainCanvas) {
// make a message box pop up with the error.
alert('Error: I cannot find the canvas element!');
return;
}
// Get the 2D canvas context.
mainContext = mainCanvas.getContext('2d');
if (!mainContext) {
alert('Error: failed to get context!');
return;
}
planePosition = new Vector(0, 0, 0);
plane = new Plane(planePosition);
}
function translate(pPosition) {
var matrix = Matrix.createTranslation(pPosition);
matrix.transform(mainContext);
}
function scale(pPosition) {
var matrix = Matrix.createScale(pPosition);
matrix.transform(mainContext);
}
function rotate(pPosition) {
var matrix = Matrix.createRotation(pPosition);
matrix.transform(mainContext);
}
function drawPlane() {
scaleVector = new Vector(0.25, 0.25, 0);
scale(scaleVector);
translate(new Vector(0, 0));
rotate(0);
plane.draw(mainContext);
}
function drawMoth() {
var moth, mothPosition;
mothPosition = new Vector(mainCanvas.width / 2, mainCanvas.height / 2, 0);
moth = new Moth(mothPosition);
moth.draw(mainContext);
}
function drawPlane() {
plane = new Plane(planePosition);
scale(new Vector(0.25, 0.25, 0));
plane.draw(mainContext);
}
function animatePlane() {
translate(planePosition.add(new Vector(100, 100, 0)));
drawPlane();
window.requestAnimationFrame(animatePlane);
}
initialiseCanvasContext();
drawMoth();
animatePlane();
}
window.addEventListener('load', onLoad, false);
Please let me know if you think it would help to see any associated methods. I have also attached the result.
A lot of things are undefined here (the Matrix and Vector objects and their methods). Please include the code if you have it, otherwise keep reading.
It seems you've got a bit of a gap in your Object-Oriented JS knowledge. I'm guessing you know a good chunk of this, but it will be worth your time to solidify your knowledge of the following:
Know how to make a new object. Understand what an object is.
Understand how to make a constructor function.
Understand that using the new keyword simply calls an object's constructor function.
Understand inheritance. The this keyword vs. prototypical inheritance.
Understand encapsulation.
Understand the Canvas API
Add comments to your code. If other people are going to read you're code, please add comments. It helps. All you really need is a single comment per function explaining what it does. Explaining what the input parameters are is useful to.
Here are some resources to get you started (and perhaps finished):
Read this (and it's two prerequisites): http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
Check out the built-in Canvas methods: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
Here are the code structures that were missing
function Vector(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
var Matrix = {
createTranslation: function() {
},
createRotation: function() {
},
createScale: function() {
},
transform: function() {
}
};
function Plane(position) {
this.add = function() {
this.draw = function() {
};
};
}
function Moth(position) {
this.draw = function() {
};
}
I know this doesn't answer your question and I'm likely about to be downvoted to hell, but I can't really help you until you understand what you're doing a bit better. Good luck!

Javascript prototype function override when x

In my case, I'm using the Phaser framework.
So in this example I'm extending the Group class of phaser. Every 'actor' class (Sprite, Group, ...) calls upon the update() prototype every few miliseconds.
My idea was to extend this function only when the application runs on a desktop (so not on a phone).
for example:
var MousePointer = function (game, parent, name) {
Phaser.Group.call(this, game, parent, name);
this.init();
};
MousePointer.prototype = Object.create(Phaser.Group.prototype);
MousePointer.prototype.constructor = MousePointer;
MousePointer.prototype.init = function () {
// ... init
};
MousePointer.prototype.update = function () {
// Do something when on desktop
};
I can't possibly use an if clausule in the update() function to check whether the player is on dekstop/tablet/phone. So is there a way to actually override the prototype on initialisation?
for example (pseudocode):
if(onPhone)
MousePointer.prototype.update = parent.prototype.update;
else
MousePointer.prototype.update = this.update;
Well, you've kind of already written the answer for yourself, haven't you? This code (not inside the init method).
if(onPhone) {
MousePointer.prototype.update = function(){//Phone implementation};
} else {
MousePointer.prototype.update = function(){//Other implementation};
}
I advise against starting off with the "regular" function and then potentially overriding it, since you're just declaring it for nothing.
I think a better way to do this would be to write two different classes that shares the same parent, and then write different update() implementations for them. Then you can just do something like:
if(phone) {
var obj = new PhoneMousePointerObject();
} else {
var obj = new DesktopMousePointerObject();
}
// ... later
obj.update()

why doesn't removeEventListener work?

I'm not sure what's wrong here, but testing in the chromium and firefox, I find that I'm doing it wrong with respect to removing an EventListener from an element in javascript.
The context is a canvas game. At first, there's a splash screen shown where you click to begin the game. After you click to begin, I want to remove the listener.
The main point of interest is the removeEventListener in the startGame function. It doesn't throw an error. And the code executes (I see the game starting message in the console and I can see that "this" is the Game instance). I'm totally confused why if I keep on clicking on the canvas runs startGame each time. The expected behavior is that clicking there does nothing once the EventListener is removed.
Help!
function Game(canvas) {
this.c = canvas;
this.ctx = this.c.getContext("2d");
this.c.width = CANVAS_WIDTH;
this.c.height = CANVAS_HEIGHT;
// Background image
this.bgReady = false;
this.bgImage = new Image();
this.bgImage.onload = function () {
window.g.bgReady = true;
};
this.bgImage.src = MAIN_BACKGROUND;
}
Game.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.g.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
document.getElementById("cnvs").addEventListener(
'click',this.startGame.bind(this),true);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Game.prototype.startGame = function() {
console.log("game starting ...");
console.log(this);
// step 1, remove the click listener for this function
// why isn't this working?!
document.getElementById("cnvs").removeEventListener(
'click',this.startGame,true);
}
...
// other stuff ...
function initialize() {
// Get the canvas
var c = document.getElementById("cnvs");
// Create a game object
window.g = new Game(c);
// Set the splash page
g.setSplash();
}
window.onload=initialize;
Further info:
I also had a version where the non-working removal was written as:
this.c.removeEventListener('click',this.startGame,true);
Same behavior as the code referenced above.
EDIT: in reply to the first answer by mczepiel
I'm trying to implement your answer like this:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.t.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
var boundFunction = this.startGame.bind(this);
document.getElementById("cnvs").addEventListener(
'click',boundFunction,true,boundFunction);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Typer.prototype.startGame = function(boundFunction) {
console.log("game starting ...");
console.log(this); // strangely, now this is an Object rather
// than Game, it still has the properties of
// Game tho
// step 1, remove the click listener for this function
// still isn't working...
document.getElementById("cnvs").removeEventListener(
'click',boundFunction,true);
}
I think I understood your suggestion, but perhaps not. The code above still doesn't remove the listener. Any help appreciated.
You'll need to store a reference to the result of calling this.startGame.bind(this) and pass that same value to both addEventListener and removeEventListener
The remove call is expecting to remove the exact same object that was added as a listener.
Likely duplicate of removeEventListener is not working and others if you want to see the same issue in various flavors.
EDIT untested off-the-cuff suggestion:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
// draw stuff
var canvasElement = document.getElementById("cnvs");
var dismissSplash = function (evt) {
canvasElement.removeEventListener('click', dismissSplash, true);
this.startGame();
}.bind(this);
canvasElement.addEventListener('click', dismissSplash, true);
} else {
// try to show splash later
}
}
Typer.prototype.startGame = function() {
// start game
}

How do I write a jasmine test for a method that contains a global variable from another class/file?

My tests fails for the following reason:
ReferenceError: Can't find variable: moving_canvas_context in file
(line 5)
I understand the reason the test is failing. It doesn't understand the variable since it is defined in a separate JavaScript file. However, it is declared globally and works in reality.
How do I write a jasmine test for this clear_canvas function?
JavaScript Canvas_Actions:
(function() {
window.Canvas_Actions = (function() {
function Canvas_Actions() {}
Canvas_Actions.prototype.clear_canvas = function() {
moving_canvas_context.clearRect(0, 0, moving_canvas.width, moving_canvas.height);
main_canvas_context.drawImage(window.background_image, 0, 0, main_canvas.width, main_canvas.height);
return window.canvas_objects = [];
};
return Canvas_Actions;
})();
}).call(this);
Jasmine Test for Canvas_Actions:
(function() {
describe('Canvas Actions', function() {
return describe('clear_canvas', function() {
return it('clears the canvases and deletes all objects', function() {
var actions;
jasmine.getFixtures().fixturesPath = "../spec/javascript/fixtures";
loadFixtures("canvas_fixture.html");
actions = new Canvas_Actions();
actions.clear_canvas();
return expect(canvas_objects).toEqual([]);
});
});
});
}).call(this);
it is declared globally and works in reality
Well, it also needs to be declared when the test runs. So you're probably missing a reference to the script where it is defined in the testing fixture html.
Also, global variables are normally not a good idea, they tend to create difficult bugs. Since you're already using jasmine as a testing framework, try to abstract the dependency on that global variable in something that you pass to your code under test. Then, use jasmine's mocking abilities to test it.
If you remove the global references from Canvas_Actions, it could look like this:
var Canvas_Actions = function(canvas) {
this.canvas = canvas;
}
Canvas_Actions.prototype.clear_canvas = function(background_image) {
var canvas = this.canvas;
canvas.getContext().clearRect(0, 0, canvas.width, canvas.height);
canvas.getContext().drawImage(background_image, 0, 0, canvas.width, canvas.height);
canvas.clearObjects();
};
You can mock the canvas argument with jasmine and test Canvas_Actions in isolation.
As can be noted, this code might unearth a Canvas class, and you might find out that clear_canvas belongs in there. Use the tests to guide your design, one step at a time.
Jordão is absolutely right, however there's an ugly option too.
Attach your global object to the window in beforeEach method. Code below probably does not work (haven't tested it), but should be good enough to understand how to work around this jasmine global object problem.
(function() {
describe('Canvas Actions', function() {
beforeEach(function () {
window.Canvas_Actions = (function() {
function Canvas_Actions() {}
Canvas_Actions.prototype.clear_canvas = function() {
moving_canvas_context.clearRect(0, 0, moving_canvas.width, moving_canvas.height);
main_canvas_context.drawImage(window.background_image, 0, 0, main_canvas.width, main_canvas.height);
return window.canvas_objects = [];
};
return Canvas_Actions;
})();
});
return describe('clear_canvas', function() {
return it('clears the canvases and deletes all objects', function() {
var actions;
jasmine.getFixtures().fixturesPath = "../spec/javascript/fixtures";
loadFixtures("canvas_fixture.html");
actions = window.Canvas_Actions;
actions.clear_canvas();
return expect(canvas_objects).toEqual([]);
});
});
});
}).call(this);
EDIT: as per comments by #John Henckel and #serv-inc apparently there might be an error (ReferenceError: window is not defined) to fix it instead of window use global like: window.Canvas_Actions change to global.Canvas_Actions
It seems like JasmineJS uses the global property. So #Jordão's answer nonwithstanding, you could replace
window.Canvas_Actions = (function() {
with
global.Canvas_Actions = (function() {

HTML Canvas Unit testing

How can I unit-test Javascript that draws on an HTML canvas? Drawing on the canvas should be checked.
I wrote an example for unit-testing canvas and other image-y types with Jasmine and js-imagediff.
Jasmine Canvas Unit Testing
I find this to be better than making sure specific methods on a mock Canvas have been invoked because different series of methods may produce the same method. Typically, I will create a canvas with the expected value or use a known-stable version of the code to test a development version against.
As discussed in the question comments it's important to check that certain functions have been invoked with suitable parameters. pcjuzer proposed the usage of proxy pattern. The following example (RightJS code) shows one way to do this:
var Context = new Class({
initialize: function($canvasElem) {
this._ctx = $canvasElem._.getContext('2d');
this._calls = []; // names/args of recorded calls
this._initMethods();
},
_initMethods: function() {
// define methods to test here
// no way to introspect so we have to do some extra work :(
var methods = {
fill: function() {
this._ctx.fill();
},
lineTo: function(x, y) {
this._ctx.lineTo(x, y);
},
moveTo: function(x, y) {
this._ctx.moveTo(x, y);
},
stroke: function() {
this._ctx.stroke();
}
// and so on
};
// attach methods to the class itself
var scope = this;
var addMethod = function(name, method) {
scope[methodName] = function() {
scope.record(name, arguments);
method.apply(scope, arguments);
};
}
for(var methodName in methods) {
var method = methods[methodName];
addMethod(methodName, method);
}
},
assign: function(k, v) {
this._ctx[k] = v;
},
record: function(methodName, args) {
this._calls.push({name: methodName, args: args});
},
getCalls: function() {
return this._calls;
}
// TODO: expand API as needed
});
// Usage
var ctx = new Context($('myCanvas'));
ctx.moveTo(34, 54);
ctx.lineTo(63, 12);
ctx.assign('strokeStyle', "#FF00FF");
ctx.stroke();
var calls = ctx.getCalls();
console.log(calls);
You can find a functional demo here.
I have used a similar pattern to implement some features missing from the API. You might need to hack it a bit to fit your purposes. Good luck!
I make really simple canvases and test them with mocha. I do it similarly to Juho Vepsäläinen but mine looks a little simpler. I wrote it in ec2015.
CanvasMock class:
import ContextMock from './ContextMock.js'
export default class {
constructor (width, height)
{
this.mock = [];
this.width = width;
this.height = height;
this.context = new ContextMock(this.mock);
}
getContext (string)
{
this.mock.push('[getContext ' + string + ']')
return this.context
}
}
ContextMock class:
export default class {
constructor(mock)
{
this.mock = mock
}
beginPath()
{
this.mock.push('[beginPath]')
}
moveTo(x, y)
{
this.mock.push('[moveTo ' + x + ', ' + y + ']')
}
lineTo(x, y)
{
this.mock.push('[lineTo ' + x + ', ' + y + ']')
}
stroke()
{
this.mock.push('[stroke]')
}
}
some mocha tests that evaluates the functionality of the mock itself:
describe('CanvasMock and ContextMock', ()=> {
it('should be able to return width and height', ()=> {
let canvas = new CanvasMock(500,600)
assert.equal(canvas.width, 500)
assert.equal(canvas.height, 600)
})
it('should be able to update mock for getContext', ()=> {
let canvas = new CanvasMock(500,600)
let ctx = canvas.getContext('2d')
assert.equal(canvas.mock, '[getContext 2d]')
})
})
A mocha tests that evaluates the functionality of a function that returns a canvas:
import Myfunction from 'MyFunction.js'
describe('MyFuntion', ()=> {
it('should be able to return correct canvas', ()=> {
let testCanvas = new CanvasMock(500,600)
let ctx = testCanvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(8,8)
ctx.stroke()
assert.deepEqual(MyFunction(new CanvasMock(500,600), 8, 8), canvas.mock, [ '[getContext 2d]', '[beginPath]', '[moveTo 0, 0]', [lineTo 8, 8]', '[stroke]' ])
})
so in this example myfunction takes the canvas you passed in as an argument ( Myfunction(new CanvasMock(500,600), 8, 8) ) and writes a line on it from 0,0 to whatever you pass in as the arguments ( Myfunction(new CanvasMock(500,600),** 8, 8**) ) and then returns the edited canvas.
so when you use the function in real life you can pass in an actual canvas, not a canvas mock and then it will run those same methods but do actual canvas things.
read about mocks here
Since the "shapes" and "lines" drawn on a canvas are not actual objects (it's like ink on paper), it would be very hard (impossible?) to do a normal unit test on that.
The best you can do with standard canvas it analyze the pixel data (from the putImageData/getImageData. Like what bedraw was saying).
Now, I haven't tried this yet, but it might be more what you need. Cake is a library for the canvas. It's using alot of the putImageData/getImageData. This example might help with what you are trying to do with a test.
Hope that helps answer your question.
I've been looking at canvas testing recently and I've now thought about a page that allows comparing the canvas to a "known good" image version of what the canvas should look like. This would make a visual comparison quick and easy.
And maybe have a button that, assuming the output is OK, updates the image version on the server (by sending the toDataUrl() output to it). This new version can then be used for future comparisons.
Not exactly (at all) automated - but it does make comparing the output of your code easy.
Edit:
Now I've made this:
The left chart is the real canvas whilst the right is an image stored in a database of what it should look like (taken from when I know the code is working). There'll be lots of these to test all (eventually) aspects of my code.
From a developer's point of view the canvas is almost write-only because once drawn it's difficult to programmatically get something useful back. Sure one can do a point by point recognition but that's too tedious and such tests are hard to be written and maintained.
It's better to intercept the calls made to a canvas object and investigate those. Here are a few options:
Create a wrapper object that records all the calls. Juho Vepsäläinen posted a such example.
If possible use a library like frabric.js that offers a higher level of abstraction for drawing. The "drawings" are JS objects that can be inspected directly or converted to SVG which is easier to inspect and test.
Use Canteen to intercept all the function calls and attribute changes of a canvas object. This is similar with option 1.
Use Canteen with rabbit which offers you a few Jasmine custom matchers for size and alignment and a function getBBox() that can be used to determine the size and the position of the stuff being drawn on the canvas.

Categories

Resources