How to Jasmine Spy on private member variables in Node? - javascript

I have a node module with a function constructor. I want to spy on a private property within that module but can't figure out how to spy on it. As soon as I create a closure around it I can't see it anymore from the test. The code...
//logger.js
function Logger(){
this.log(str){...}
}
//mainMethod.js
var Logger = require('Logger');
function mainMethod(){
var logger = new Logger();
logger.log('error')
}
exports.mainMethod = mainMethod;
//my test
it('when main method is called then logger will log "error"',function(){
spyOn( ... ?)
expect(logger.log).toHaveBeenCalledWith('error');
}}
Clearly the test does not work. So I don't really know how to spy on that logger. I can't use a singleton since each node request must use a new object. If this was C# I would simply use dependency injection and then spy on the interface. But everywhere I read that node does not need a DI container. Please can someone help?

Related

How to mock a new function/object creation call in javascript using Jasmine?

I want to mock an object instantiation call within another function in Javascript using Jasmine JS along with Karma. The code snippet looks like this:
ClassA = function(){
var objB = new ClassB(); // How to fake this call?
function method(){
// Want to test this method
}
}
ClassB = function(){
}
I would recommend you to use Sinon.js:
http://sinonjs.org/
We sinon you can declare spies and stubs, allowing you to check if a function (spy) was called or to inject functions returning certain values (stubs).
Also if you want to mock the whole class B I would recommend you to inject it in the class constructor (dependency injection) this way you can inject your mock during test.

new object in constructor from class undefined

I'm creating a new object from a class in a constructor, and whenever it runs I get an error that operate is undefined in the method, though it is defined in the constructor. Operate itself is thoroughly tested and works great in a separate context so that's not the problem. I'm building it with Babel, not running it directly in Node 7.0.0
import Operate from "./operate"
export default class {
constructor(Schema) {
this.schema = Schema
this.operate = new Operate(this.schema)
console.log(this.operate.run) // <- Logs just fine
}
update(req, res) {
console.log(this.operate.run) // <- Nada
this.operate.run(req.body)
.then(value => {
res.status(200).json(value)
})
}
This feels like I'm missing something fundamental. I've heard this isn't a great pattern anyway, so please feel free to suggest a better way. Thanks so much in advance.
UPDATE: This is how update is being used. I don't suspect there's any problem here, as it has worked just fine when I had been importing controller as a function from another module, instead of a class
import {Router, } from "express"
import Controller from "../controller"
import User from "./user.model"
let controller = new Controller(User)
let router = new Router()
router.post("/", controller.update)
module.exports = router
Change from this:
router.post("/", controller.update)
to this:
router.post("/", controller.update.bind(controller))
When you pass controller.update it only passed a pointer to the method and any association with the controller object is lost. Then, when that update method is called later, there is no association with the appropriate object and thus the this handler in the method is wrong and you get the error you were seeing.
You either force the binding of the update method within the object or when you pass the method elsewhere that might not be called correctly, you can use the above structure to pass a bound version of the method.
You could also modify your definition of the update method to permanently bind it to your object in the constructor by adding this to the constructor:
this.update = this.update.bind(this);

Class and scope in ember js

Im building an ember application consuming a couple of web services. And I'm trying to pass a class object throw the config/environment file doing this:
var myclass = require('myclass-package');
var ENV = {
APP: {
MY_OBJ_CLASS: new myclass({
//CONSTRUCTOR PARAMS...
PROP1: "HELLO"
})
}
}
In my ember app/controllers I'm doing this:
import ENV from '../config/environment';
var obj1 = ENV.APP.MY_OBJ_CLASS;
I can see that the object is instantiated if I console.log the class object but when I try to access to the properties and functions, I can't and I get back this error:
var data = obj1.my_function_class({param1:1});
console.log(data)
TypeError: obj1.my_function_class is not a function
But the function exist...
What is the way to access to my class properties and functions?
config/environment.js is a special file. It is executed in Node, then serialized to be made available for the browser app.
You should not store any functionality in that file.
Put your class into a proper Ember module. Depending on what you're trying to achieve, that could be a service, a model, an util, etc.
Provide more details on your original problem, not your attempted solution. See http://xyproblem.info .

Unit testing with Jasmine, mocking a constructor

I'm unit testing JavaScript with Jasmine and I am running into some problems.
I have a large file to test and it has a lot of dependencies and those dependencies have their own dependencies. Because of said dependencies I want to mock all I can. There lies the problem. How can I mock a constructor so that it includes the methods that belong to it?
Lets say I'm testing a method createMap of class Map:
In that createMap method it calls for Layers class constructor using
var layers = new Layers()
I'm spying on it using
spyOn(window, 'Layers').and.callThrough()
That works fine but later in the createMap method it calls for layers.addLayer() where addLayer is a method of Layers class. Problem is that because I mocked the Layers call it doesn't recognize the addLayer method.
Is there a way to mock it so that it includes all the methods of the called class or is my only option to stub the whole Layers class or not mock it?
Or what would be a good way to handle this? I've tried to spyOn(Layers, 'addLayer') but there it says that no method addLayer is found.
I'm sorry if it's confusing a bit. I had trouble thinking how should I ask it.
IMO, it's unnecessary to spy on window, since you can easily shadow the variable in local scope by creating a spy object with the same name:
describe('Map', function () {
var Layers;
beforeEach(function () {
Layers = function () {
// alternatively, you could move this to Layers.prototype
this.addLayers = jasmine.createSpy('Layers#addLayers');
};
});
/* ... */
});
If you want an automatic mocking and using CommonJS modules, you may try Jest framework, which is built on top of Jasmine.
Let's talk in terms of example classes you have provided.
You're writing a test suite for Map. All its dependencies (in example we have only Layer) MUST be mocked. Because in a unit test you're supposed to test one layer, as small functionality as possible. It means that you should provide such a mocked Layer constructor that exposes interface used in Map. For example:
function Layers() {
this.addLayer = sinon.spy();
}
In this test suite only Map class should remain "real". I.e. it's code must not be altered. And with such mockups like Layer you make sure that you do not trigger any interaction with real-code dependencies (own-written dependencies should be tested in a different test suite, also make sure you don't try to test framework functions, like $tate.resolve, $inject etc.). If class Map is complicated and has multiple dependencies, investigate sinon features that help automate this process, for example sinon.mock
If you ever transpile class syntax to a es3 or another pre-2015 dialect you will discover something interesting.
class a {
constructor(){
...
}
index()
{
...
}
}
Becomes:
var a = /** #class */ (function () {
function a() {
...
}
a.prototype.index = function () {
...
};
return a;
}());
This same implementation is used by later standards but masked by the 2015 class syntax. In other words a.index doesn't exist instead it's defined as a.prototype.index. Thus you need spyOn(a.prototype, 'index') to spy on it.
Change spyOn(Layers, 'addLayer') to spyOn(Layers.prototype, 'addLayer')

How do I specify a Sinon.JS spy function as part of a class definition?

I am writing a Backbone.js app using Require.js and Backbone.Marionette and testing it using Mocha with Chai, Sinon, and Sinon-Chai. I've been generally using Jarrod Overson's Backbone Marionette with Require.JS TODO sample as a reference for application structure and Jim Newbery's posts on testing Backbone apps as a reference for unit testing.
My problem is trying to test adding a Marionette ItemView to a Marionette Application object. I figured the best way to test that the ItemView is added is watch for its render() method to be called. Since Marionette provides a default render() implementation, I thought it best to just use a Sinon spy for the onRender() callback.
I used Squire.JS to return a stub class for my ItemView as shown below:
define(['Squire', 'backbone.marionette'], function(Squire, Marionette) {
describe('App', function() {
var testContext;
beforeEach(function(done) {
testContext = {};
testContext.injector = new Squire();
testContext.renderSpy = sinon.spy();
testContext.injector.mock('app/views/Header', function() {
var stub_template_html = "<div></div>";
var HeaderStub = Marionette.ItemView.extend({
template: function(serialized_model) {
return _.template(stub_template_html);
}
});
return HeaderStub;
});
testContext.injector.require(['app/app'], function(app) {
testContext.app = app;
done();
});
});
it ('Should add a Header view to the \'header\' region', function() {
testContext.app.start();
expect(testContext.renderSpy).to.be.called();
});
When I run Mocha via Chrome, I get the error I expect: "expected spy to have been called at least once, but it was never called." However, if I specify the Sinon spy function as the onRender() callback, as shown below
var HeaderStub = Marionette.ItemView.extend({
// ...
onRender: testContext.renderSpy
});
I get an error stating that the called() method is not a function.
Is there a way to specify a Sinon spy function as a method in a class definition? Alternatively, is there a better way to test this code? I'm fairly new to JavaScript, so this may be a more general problem rather than a Sinon-specific problem.
Any help is appreciated.
I'm not sure what assertion library you are using, but to check if a function you spy on with sinon was called, you have to check the called property of the spy. See http://sinonjs.org/docs/#spies
spy.called
true if the spy was called at least once
So your assertion should look like this:
expect(testContext.renderSpy).to.be(true);

Categories