Shane Brunson
Shane Brunson

Reputation: 31

Using Jasmine to spy on document.createElement throws error from angular mock

I have an Angular service that handles the webcam. Here is the function I'm trying to test:

this.takePicture = function() {
    var canvas = document.createElement('canvas');
    canvas.width = this.width;
    canvas.height = this.height;

    var context = canvas.getContext('2d');
    context.drawImage(this.videoElement, 0, 0, this.width, this.height);

    return canvas.toDataURL('image/jpeg', 100);
};

I'm trying to mock the call to document.createElement and return a fake canvas object. Here is my test:

it('should draw an image', function() {
    var drawImageSpy = jasmine.createSpy('drawImage');

    var canvas = {
        getContext: jasmine.createSpy('getContext').and.returnValue({ drawImage: drawImageSpy }),
        width: 0,
        height: 0,
        toDataURL: jasmine.createSpy('toDataUrl').and.returnValue('data-uri')
    };

    document.createElement = jasmine.createSpy('createCanvas').and.returnValue(canvas);

    WcCameraService.takePicture();

    expect(drawImageSpy).toHaveBeenCalled();
});

Here is the error I'm getting:

TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
    at TypeError (native)
    at Function.jQuery.extend.buildFragment (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/jquery/dist/jquery.js:5565:24)
    at Function.jQuery.parseHTML (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/jquery/dist/jquery.js:9923:18)
    at jQuery.fn.init (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/jquery/dist/jquery.js:2774:33)
    at Object.jQuery [as element] (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/jquery/dist/jquery.js:73:10)
    at $get (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/angular-mocks/angular-mocks.min.js:6:18224)
    at Object.e [as invoke] (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/angular/angular.min.js:39:193)
    at C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/angular/angular.min.js:41:10
    at Object.d [as get] (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/angular/angular.min.js:38:394)
    at Object.<anonymous> (C:/Projects/Accurev/WebCOE_FRF_DEV/src/bower_components/angular-mocks/angular-mocks.min.js:6:21105)

Does anyone have any idea why this is happening? I was looking in angular-mocks and it seems to be failing at this point:

if (window.jasmine || window.mocha) {

...

  if (injector) {
    injector.get('$rootElement').off();
  }

Upvotes: 3

Views: 5230

Answers (3)

AnthonyAtVIF
AnthonyAtVIF

Reputation: 11

I had the same issue and have solved it by using both callThrough() and withArgs() methods to only spy my specific case and let other calls to be done normally.

When you spy a method of the document object, you alter every calls that could be done by other part of your code or vendors' codes. Using withArgs(), you limit the side effects.

Here is my example (I was trying to test a modal that download a file from a text built in the web page) :

var fakeElement = {
    setAttribute: function (name, value) { fakeElement[name] = value; },
    click: function () {}
};
spyOn(fakeElement, 'click');
spyOn(document, 'createElement').and.callThrough().withArgs('a').and.returnValue(fakeElement);
spyOn(document.body, 'appendChild').and.callThrough().withArgs(fakeElement);
spyOn(document.body, 'removeChild').and.callThrough().withArgs(fakeElement);

Upvotes: 0

Alejandro Barone
Alejandro Barone

Reputation: 2161

Its kind of weird to spy on that. But -in greater versions of angular- you can accomplish it by just spying into the createElement method. You can join this with a returnValue with a jasmine spy to expect certain calls. Like this:

const toDataUrlSpy = jasmine.createSpy('dataUrl');
spyOn(document, 'createElement').and.returnValue({
  width: 0,
  height: '',
  getContext: () => ({ drawImage: () => {} })
  toDataURL: toDataUrlSpy
});

service.takePicture();
expect(toDataUrlSpy).toHaveBeenCalledTimes(1);

Cheers!

Upvotes: 0

MBielski
MBielski

Reputation: 6620

To the best of my knowledge, document.createElement is immutable. You really shouldn't be spying on it in the first place. It's a built-in function that will perform the same way every time. Your other spies look fine.

Upvotes: 0

Related Questions