Mocha - Accessing named functions with ti-mocha - javascript

I'm using a hybrid version of mocha, ti-mocha, to build unit test for a Titanium SDK-based app. I'm completely new to BDD and mocha, so the API learning curve is quite steep. Here's my issue with a stripped-down test harness. I can access the index.js module, its methods and properties. However, I don't know how to access the named functions, in this case doClick(), within that module.
I'm using mocha + should.js. In the test harness below, context "index" passes, but context "doClick" fails. I'm so new to this API, that I'm not sure if I even framed the question properly. How do I access the functions within the module?
index-mocha.js
// creates the "mocha" global necessary to run a test suite anywhere in your app
var should = require('should');
module.exports = function(index) {
// create the test suite
describe('mochatest', function() {
context('index', function() {
it('index exists', function() {
should.exist(index);
});
it('index.open function', function() {
should(index.open).be.a.Function;
});
it('id = index', function() {
index.id.should.equal('index');
});
});
context('doClick', function() {
it('doClick exists', function() {
should.exist(index.doClick);
// return;
});
it('doClick is a function', function() {
should(index.doClick).be.a.Function;
});
});
});
var outputFile = Ti.Filesystem.getFile(Ti.Filesystem.tempDirectory, 'results.json');
outputFile.createFile();
mocha.setup({
reporter: 'ti-spec', // the reporter to use with your tests
outputFile: outputFile, // write results to the given Ti.Filesystem.File file
quiet: false // if true, suppress all console logging
});
// run the tests
mocha.run();
};
index.js
function doClick(e) {
alert($.label.text);
}
if(runTests){
require('ti-mocha');
$.index.addEventListener('open', function(){
require('index-mocha')($.index);
});
}
$.index.open();

Nice to see someone playing around with testing in Titanium :)
Just to clarify a thing, the variable $ refers to an instance of your current controller. Also, Alloy gives you references to view elements for which you have defined an id via this variable; This may be seen as a little sugar as all those views are accessible through $.getViews().
Therefore, all functions defined inside your controller file are accessible only from within that controller. If you want them to be accessible from the outside, the easiest and cleanest way is to exports them.
This can be done in two ways:
By directly add them to the controller object
$.doClick = doClick;
By using the exports variable
exports.doClick = doClick;
The result will be exactly the same as, during the compilation, Alloy will merge the exports variable (which is, initially, only an empty object) with your controller a.k.a $.
Then, just pass your controller through your require instead of the index view, to have access to both views and newly added listeners.

Related

Is there a way to get current Mocha instance and edit options at runtime?

Let's say you have a simple mocha test:
describe("Suite", function(){
it("test",function(doneCallback){
// here be tests
});
});
In this test I can change the timeout by adding this.timeout(VALUE); anywhere within the describe function.
However, besides the timeout value, there are plenty of other Mocha options that can be exclusively declared either from the command line or from a mocha.opts file that lives in the test folder (./test/mocha.opts).
What I want is to change some of these options at run-time (for example, the reporter) and not in command line / mocha.opts file.
From my research of what's possible, I found that there is an article explaining how you can use mocha programmatically, which would allow changing these options at run-time, but you need to create the Mocha instance yourself, whereas in an ordinary test one doesn't have direct access to the Mocha instance.
So, is there a way to get the Mocha instance from an existent test and change some of these options like reporter at run-time during a test?
I would like to have an option that doesn't require to modify the source code of Mocha in any way (I suppose I could tamper with the Mocha instance to implement a way to get an instance directly in the Mocha constructor).
The best way that you can achieve that is by using Mocha as per the wiki link that you have already referenced, which is using Mocha programmatically.
So to your inquiry on changing the reporter parameter here is a brief example that would do what you want, in order to run the tests against a theoretically already existing file named test-file-a.js that contains your tests:
var Mocha = require('mocha'),
mocha = new Mocha(),
path = require('path');
mocha.addFile(path.join(__dirname, 'test-file-a.js'));
mocha
.reporter('list')
.run();
Besides that there are plenty other options that you can use and also there are some listeners for events, like test that you may want to do something during a test, for example:
mocha
.reporter('list')
.ui('tdd')
.bail()
.timeout(10000)
.run()
.on('test', function(test) {
if (test.title === 'some title that you want here') {
//do something
}
});
Please note that you can define the options per each Mocha instance that will run again a test suite, but not during the runtime of a test suite, so for example if you start your tests for test-file-a.js with the option reporter('list') as above you cannot change it while the tests are running to something else, like you may do for example with the timeout option where you can do this.timeout().
So you would have to instantiate a new Mocha instance as the examples above with different options each time.
No, you cannot. without changing the code.
In short, mocha is created in a scope you cannot access from tests. Without going in details, the objects provided in your scope cannot change the options you want. (You cannot do this: link)
But there is a way to define your own reporter and customize the output for each test:
Create a file called MyCustomReporter.js:
'use strict';
module.exports = MyCustomReporter;
function MyCustomReporter (runner) {
runner.on('start', function () {
var reporter = this.suite.suites["0"].reporter;
process.stdout.write('\n');
});
runner.on('pending', function () {
process.stdout.write('\n ');
});
runner.on('pass', function (test) {
var reporter = this.suite.useReporter;
if(reporter == 'do this') {
}
else if(reporter == 'do that'){
}
process.stdout.write('\n ');
process.stdout.write('passed');
});
runner.on('fail', function () {
var reporter = this.suite.useReporter;
process.stdout.write('\n ');
process.stdout.write('failed ');
});
runner.on('end', function () {
console.log();
});
}
When you run mocha, pass the path of MyCustomReporter.js as reporter parameter(without .js), eg:
mocha --reporter "/home/user/path/to/MyCustomReporter"
The default mocha script actually tries to require a reporter file if it is not found in the default ones(under lib/reporters), github link
Finally, in your tests, you can pass some parameters to customize the output of your reporter:
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
this.parent.reporter = 'do this';
it('should return -1 when the value is not present', function() {
this.runnable().parent.useReporter = 'do this';
assert.equal([1,2,3].indexOf(4), -1);
});
});
});

Working with global window variable in mocha js from node

I am new to js unit testing and I am trying to use mocha for my backbone contact manager tutorial that i found at this github repo. However, i have a global window.ContactManager variable that I firsted wanted to test whether it exists and then test the router.on functionality inside the start function later. The variable looks like so:
window.ContactManager = {
Models: {},
Collections: {},
Views: {},
start: function(data) {
var contacts = new ContactManager.Collections.Contacts(data.contacts),
router = new ContactManager.Router();
router.on('route:home', function() {
router.navigate('contacts', {
trigger: true,
replace: true
});
});
router.on('route:showContacts', function() {
var contactsView = new ContactManager.Views.Contacts({
collection: contacts
});
.....
My test that does not work:
var expect = require ('chai').expect;
describe("Application", function() {
it('creates a global variable for the name space ContactManager' , function () {
expect(ContactManager).to.exist;
})
});
How do I test and access a global window variable existence in mocha from running the tests in the console?
You are ignoring the difference between running JavaScript code in the browser and running JavaScript code in Node.
In the browser, the window name is a reference to the object which holds all your global variables. So when you do foo = 1 in the outermost scope, you declare a global foo, which is also accessible as window.foo. Conversely, if you assign a new field like this: window.bar = 1, then you have a new global called bar.
In Node, your global object is accessed as global. So if you do foo = 1 in the outermost scope, foo is also accessible as global.foo. And if you do global.bar = 1, you have a new global named bar.
Your code shows that you modify a window object, which does not appear to be a reference to the global object. Options:
Run Mocha in the browser instead of in Node. See Mocha's documentation.
Set your Node environment so that it mimics enough of a browser environment to satisfy node. Setting a global window variable to be a equal to global might be enough but I don't know Backbone enough to know whether Backbone will be happy with this.
Run your Backbone-based code in jsdom. Jsdom provides realistic window and document, as if your code was running in a browser, but it has its limits. I don't know whether Backbone would be happy with those limits.
Another solution would be to use https://www.npmjs.com/package/window-or-global
import React, { Component } from 'react'
// in node, you'll get the global object instead of crashing by an error
import root from 'window-or-global'
class MyComponent extends Component {
// this method is only invoked in the browser environment
componentDidMount() {
root.addEventListener(/*...*/)
}
componentWillUnmount() {
root.addEventListener(/*...*/)
}
render() {}
}
// VoilĂ . Enjoy your universal react component! ;)
// No more 'window is not defined' errors when you render your component
// on server side.
To install, run npm install --save window-or-global.
Running tests on server (for example with mocha-webpack) is way more faster than in a browser.

Accessing a Meteor Template Helper Function in Jasmine for Integration Testing

I'm trying to run Jasmine client integration tests on a meteor project. I'm using meteor 0.9.4, and the sanjo:jasmine package for Jasmine.
I have written a test which looks like:
describe("Template.dashboard.tasks", function() {
it("ela displays correct assessment", function() {
Session.set("selected_subject", "math");
Session.set('selected_grade', "1");
tasks = Template.dashboard.tasks();
expect(true).toBe(true);
});
});
I get an error before it can get to the end of the test:
Cannot read property 'tasks' of undefined
This means that Template.dashboard does not exist within the scope of this test.
Template.dashboard.tasks() is a helper function which works completely, and it is in a js file within a view folder. Regular Jasmine tests work as expected, but as soon as I try to use one of my own functions from another file, it doesn't work.
My question is: Is there something I need to do to give the Jasmine test access to my template helper functions?
In Meteor, Template helper functions used to be formatted like this:
Template.dashboard.tasks = function () {
...
};
But that has been deprecated, and the new format is:
Template.dashboard.helpers({
tasks: function(){
...
}
});
In Jasmine, with the previous formatting, you could access helper functions like:
Template.dashboard.tasks();
But now you must call helper functions like this:
Template.dashboard.__helpers[' tasks']();
Sanjo (the original author of the meteor-jasmine repo) suggested using a function like this to make it easier to call helper functions (especially if the syntax ends up getting changed again):
function callHelper(template, helperName, context = {}, args = []) {
template.__helpers[` ${helperName}`].apply(context, args);
}
An updated answer to this question for Meteor 1.3 (sorry I use mocha, but it does not affect the answer):
Template.foo and Template.foo helpers won't be eagerly set up when testing, so you need to import foo.html then foo.js.
Here is an example :
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Foo } from '/collections/foo.js';
import { assert } from 'meteor/practicalmeteor:chai';
import './foo.html'; // now Template.foo is defined
import './foo.js'; // now Template.foo.__helpers[' bar'] is defined
describe('foo handlers', () => {
it("Should test bar", () => {
// don't forget the space, helper key is ' bar' not 'bar'
var bar = Template.foo.__helpers[' bar'].apply({foo:"bar"},3);
assert.Equal(bar,'bar');
});
});
Of course as said before, you should definitely encapsulate the weird Template.foo.__helpers[' bar'].apply(context,args) into a nice, clean helper.
tests on server part are running nicely from start, and there is indeed one more thing to do in order to run tests on the frontend part. I'll try to find you that.
In addition, consider reading or reading again the famous and explicit article from Dr. Llama's Blog related to Jasmin/Meteor : Bullet-proof Meteor applications with Velocity, Unit Testing, Integration Testing and Jasmine

How to remove Backbone Marionette module?

I'm testing my Backbone Marionette modules. Before each test I want to setup a module on the application, and in the teardown process want to remove it completely.
I found only how to stop a module, but it is not enough, it doesnt remove event listeners, module object on application etc. And besides invoking the module definition again if more test cases exist, it doesnt create a new module, just applies it to the existing.
var application = new Backbone.Marionette.Application();
beforeEach(function() {
application.module('MyModule', function() {
// module definition goes here
})
});
afterEach(function() {
// i want something like this
application.remove(MyModule)
});
// assume you know the name of the module
delete application['MyModule'];
// otherwise
var module = application.module('awesomeModule', function(){});
delete application[module.moduleName];
MyModuleYou could define it after and just start and stop when on before and after each methods.
var application = new Backbone.Marionette.Application();
application.module('MyModule', function() {
// module definition goes here
})
beforeEach(function() {
application .module('MyModule').start();
});
afterEach(function() {
application .module('MyModule').stop();
});

Global variables in Karma test runner

I have a global variable defined in my main template, which I use to store information bits from the back end, such as the environment context path. I can't move that variable inside a service.
How can I expose that variable to Karma when I run the unit tests?
You either declare that global variable within your test file:
var global = "something";
describe('Your test suit', function() {
...
});
or add a Javascript file where it's defined to your karma.conf.js file:
// list of files / patterns to load in the browser
files: [
...,
'file-containing-the-global-variable.js'
],
If you are coming from Angular 2+ the only way I found that works is to create the variable or object globally using window:
Google Recapthca Loaded from a script:
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
In this example a call to the google Recaptcha will create a global variable called grecaptcha.
In our component we decide to render a new Google Recaptcha. Of course since we do not declare grecaptcha we need to use declare var to say hey I promise it is declared somewhere:
declare var grecaptcha;
private CreateGoogleCaptcha() {
grecaptcha.render('recaptcha', {
sitekey: this.siteKey,
callback: this.GoogleCaptchaCallback,
badge: 'inline'
});
}
private GoogleCaptchaCallback(token) {
// Handle Callback Logic
}
Now that our function works we decide to run a test but of course we would like to mock our grecaptcha as we don't have control over it.
So we name our global variable we would like to create and add the functions we would like:
beforeEach(() => {
fixture = TestBed.createComponent(GoogleRecaptchaComponent);
component = fixture.componentInstance;
window['grecaptcha'] = {
render() {
console.log('mocked global variable and function');
}
}
}
Update:
Creating the global variable in the beforeEach is fine but what about when it has some sort of callback function such as above that calls a function from your component? Easy enough we just move the login to our test and in our mock we set it to our components function:
it('should ', () => {
window['grecaptcha'] = {
render: function() { GoogleRecaptchaComponent['GoogleCaptchaCallback']('token'); }
};
spyOn<any>(GoogleRecaptchaComponent, 'GoogleCaptchaCallback');
GoogleRecaptchaComponent['CreateGoogleCaptcha']();
expect(GoogleRecaptchaComponent['GoogleCaptchaCallback']).toHaveBeenCalled();
});
Note: spyOn<any>: The <any> is used so we can refence without error the function because it is private, otherwise we will get a typescript error;
The first solution didn't work for me in Angular 2.1.x. It simply would not recognize the variable in my imported service. What I had to do was put my environment variable in my karma-test-shim.js file and remove var so it would be globally available.
Mine looks like this:
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('reflect-metadata');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy'),
require('zone.js/dist/sync-test'),
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
// Add environment variables here so that tests will inject them in source code
API_URL = 'http://localhost:8080/api/';
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('#angular/core/testing');
var browser = require('#angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(
browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting()
);
I was trying to integrate angular v13 with google-place-autocomplete without any npm package, just vanilla google-place-autocomplete. When i tried to test, karma said: '"google" is not defined'.
So i found the awswers above, and it worked for me like this:
Created a file in root directory (in my case, i named: google-place.js)
Added a mock object with the instructions that is used in component.
class Autocomplete {
constructor(input, options) {
const addListener = (eventName, callback) => { }
const getPlace = () => { }
return { addListener, getPlace }
}
}
var google = {
maps: {
places: {
Autocomplete: Autocomplete
}
}
};
Added in karma.conf.js a property called "files", and end up like this:
files: [
"./google-place.js"
]
And it was not necessary to add a declare var google in every spec. Is global now.

Categories

Resources