Destroy a Backbone.Router and all Side effects after Creation - javascript

Let's say you want to be able to create a Backbone.Router instance that calls Backbone.history.start, destroy all traces of it, and create it again.
I need to do this for unit testing.
The problem is that creating a Backbone Router has global implications. Like Backbone.history, which is undefined until you make it.
var ReversibleRouter = Backbone.Router.extend({
initialize: function(){
_buildAppPaths(this.options) // Implementation not shown. Builds the application paths
Backbone.history.start()
},
destroy: function(){
// TODO: Implement this
}
})
I'd like to be able to create and completely destroy so that I can do some unit testing on my application's implementation of the Router.
It it enough to call Backbone.history.stop() and set Backbone.history = undefined?

If you're looking to do more full stack integration types of testing, you can reset your application's router by using:
Backbone.history.stop()
Backbone.history = undefined // Could also probably use delete here :)
This has been successful so far--our tests probably instantiate and kill the router 5 times during our qunit testing. Works great.
Maybe mocking like #AdamTerlson is saying is the better way to go long term, but I'm still learning unit and integration testing philosophy. I don't mind having a full stack test or two, though.

This might be cheating, but I'm of the opinion you should be mocking functions outside of your unit of functionality that your testing and be especially sure your unit tests are not be allowed to hit the live DOM, server or the browser.
Look into SinonJS, it's great for this.
Here's an example of testing the initialize method of the router without ever affecting the browser history state.
test('...', function () {
// Arrange
var buildAppPaths = sinon.stub(ReversibleRouter.prototype, '_buildAppPaths');
var startHistory = sinon.stub(Backbone.history, 'start');
var options = {};
// Act
new ReversibleRouter(options);
// Assert
ok(buildAppPaths.calledWith(options));
ok(startHistory.calledOnce); // or ok(Backbone.history.start.calledOnce);
});
Under this approach, you have successfully asserted that all calls were made to external units of functionality, including params. At that point, you can trust that the external call will do its job (proven by additional unit tests).
To make calls outside of this one unit would be doing integration tests and not unit tests.

Related

AngularJS 1.7 Module Injection For Tests Error

My AngularJS (1.7.x) application has a custom filter that I want to write tests around it, and I decided to use Jest to perform this task.
My main issue is that as I follow this tutorial by Curt, I am struggling to properly get the filter (which has no outside dependencies so I thought it was the prime target to introduce unit tests with) to load within the test harness.
For starters, here is a simplified version of the filter for the purpose of this question:
angular.module('app.module').filter('takeTheThing', () =>
function (parameter1) {
return `${parameter1} thing!`;
}
);
And, after following the tutorial above, as well as reading up on another SO question specific to testing AngularJS filters, I have attempted every conceivable version of my simple test file as follows but receive a cryptic error message from Angular about it:
require('../node_modules/angular/angular.min.js');
require('../node_modules/angular-mocks/angular-mocks.js');
//Commenting or un-commenting this, results in module-related injector errors:
//require('./takeTheThing.filter.js');
describe('The thingerizer', () => {
let $filter;
beforeEach(angular.mock.module('app.module'));
//Injecting this specifically, or _$filter_ still errors:
beforeEach(inject(takeTheThingFilter => {
$filter = takeTheThingFilter;
}));
//Injecting here instead of in the beforeEach, same issue
it('should give me something', () => {
//Calling the specific filter or thru the Angular $filter... Same
const actual = $filter(1);
expect(actual).toEqual('1 thing!');
});
});
I'm pulling my hair out, but there's something quite basic I'm missing in regards to the test setup (specifically, how to load the app "correctly" without whole-hog loading my entire application). What gives?
AngularJS: 1.7.5
Angular Mocks: 1.7.6
Jest: 23.6.0 (I am using gulp-jest but even when I directly call jest from within the bin folder, I get the exact same errors, so I omitted most of those details here)

How to trace function calls in Marionette

Somewhere in the global scope:
let App = Backbone.Marionette.Application.extend({});
window.Ext = new App();
Inside Module A:
Ext.vent.trigger('Tracked:Email:send', {
from_email: tracking_data.sender,
to_emails: tracking_data.to,
cc_emails: tracking_data.cc,
email_id: tracking_data.email_id || '',
template: tracking_data.template_id || '',
subject: tracking_data.subject
});
Inside Module B:
Ext.vent.on('all', function (evt_name, params) {
// something else...
console.log(params);
}
The object's properties (from_email, to_emails & cc_emails) are undefined when I call console.log in Module B.
I've tried to debug this using console.trace, but the console doesn't show any functions that are involved in changing the object. I've also tried using Object.observe to capture the act of changing the object but no changes are detected.
Can some please teach me some debugging techniques to trace function calls and events in Marionette.
The scenario is:
our codebase is huge.
I'm a new guy at our company so I'm not sure if there are other functions or events that are involved.
I'm the only front end developer right now.
The good news: there's nothing special about Marionette here. Everyday JavaScript debugging techniques will help you out.
The bad news: as you're aware, debugging large, event-driven applications is difficult.
You could take these steps in Chrome dev tools (YMMV with other browsers):
Split the App.trigger line to get a reference to the parameters object:
let options = { from_email: ... }
App.vent.trigger('Tracked:Email:send', options);
Set a breakpoint on Ext.vent.trigger and run your code.
When execution pauses at the breakpoint, check that options contains the expected values. If not, the problem is somewhere before App.vent.trigger. Look up the call stack for any functions that affect tracking_data. You may need to check the Async checkbox if it is populated by asynchronous code.
If options contains the values you expect, carry on...
Whilst execution is paused use the console to save a global reference to your parameters object:
> OPTIONS = options
Add OPTIONS as a watch expression. Expand the item so you can watch changes to its properties.
Step through (including Marionette and Backbone code) until you see the OPTIONS properties change. If you don't see Marionette code when you step into App.vent.trigger you may need to remove Marionette from your black boxed libraries.

Component/Integration testing Backbone views and more

I'm looking to write some tests that test the wiring between Backbone views and their models. I essentially want to load up my view with a model and make sure everything is peachy (events properly triggered/handled, elements added to the DOM, etc). I see this as different from acceptance/functional/e2e testing, but larger than simple unit tests. In other words, I'm not looking to write:
var browser = new Browser()
, fakeData = readFixtures("persons.json");
fakeAPIResponse('/persons', fakeData);
browser.visit("http://localhost:3000/", function () {
  assert.ok(browser.success);
var persons = browser.queryAll(".persons li");
  assert.lengthOf(persons, 20);
});
but rather something like
var router = require('routers/main'),
UserModel = require('models/user'),
AccountView = require('views/account');
...
# In a test
var model = new UserModel({ userId: 1 });
router._showView(new AccountView({ model: model });
expect(document.getElementsByClassName('account-panel')).to.have.length(1);
model.set('name', 'Test Testerson');
expect(document.getElementById('name-field').value).to.equal('Test Testerson');
Maybe I'm way off and should just put together some end-to-end tests but this seems to me like it'd be a valuable way of testing. My question is: how can I accomplish this? I need a full DOM, so I'm thinking that these should run in something like PhantomJS; the DOM should be reset before each test, but it seems silly/inefficient to have the browser navigate to a new page for each individual test. Is there a framework out there for running a test this way? Feel free to tell me I'm wrong for wanting this.
We are doing this using casperJs. Casper will give you the full DOM, but this should not be the reason the choose integration test over unit test. When I find myself going way out of my way to create a test environment, I tend to choose integration.
However, having said that, I think you can get away with doing a unit test and here is how I would do it. We are using testem with chaiJs assertions for such tests. If you setup a fixture, you can do something like this (and you'll have the full DOM, and ability to test events and everything else listed in your OP).
beforeEach(function () {
var theModel = new Backbone.Model(),
theViewToTest = new TheViewToTest({
model: theModel,
el: $(fixtures.get('some-fixture.html'))
});
theViewToTest.render();
this.theViewToTest = theViewToTest;
});
describe('Checking event handlers', function () {
it('Should fire some events', function () {
// I would setup a spy here and listen to the button handler
this.theViewToTest.$el.find('.some-button').trigger('click');
// Assert the spy was called once
});
});

Mocha tests on focus-related behaviors (Backbone/CoffeeScript app)

I have a Backbone app written in CoffeeScript. I'm trying to use Mocha (with Chai and Sinon) to write tests for DOM-related behaviors, but it seems that hidden fixtures (I'm using js-fixtures now but I've also tried this unsuccessfully with a hidden '#fixtures' div) don't register certain DOM-related behaviors which makes testing certain types of DOM-related behaviors (seemingly) impossible.
For example, my main app view has several subviews which are never rendered at the same time: when the app view renders subview A, it remembers the focused element of the currently active subview B (#_visibleView), saves that information on subview B, closes the subview B, and then renders subview A.
_rememberFocusedElement: ->
focusedElement = $ document.activeElement
if focusedElement
focusedElementId = focusedElement.attr 'id'
if focusedElementId
#_visibleView?.focusedElementId = focusedElementId
This works when I test it manually, but when I try to write unit tests for this behavior they fail because I can't set focus (e.g., via $(selector).focus()) to an element in a hidden div/iframe. (I have the same issue with functionality which listens for window resize events.)
I thought that if I changed $ document.activeElement to #$ ':focus" I might get different results, but that doesn't fix the issue.
Here is what the relevant parts of my Mocha (BDD) tests look like. This spec will print TEXTAREA to the console and then undefined, indicating that there is a textarea with id='transcription' but I can't set focus to it.
beforeEach (done) ->
fixtures.path = 'fixtures'
callback = =>
#$fixture = fixtures.window().$ "<div id='js-fixtures-fixture'></div>"
#appView = new AppView el: #$fixture
done()
describe 'GUI stuff', ->
it 'remembers the currently focused element of a subview', (done) ->
#appView.mainMenuView.once 'request:formAdd', =>
#appView._visibleView.$('#transcription').focus()
console.log #appView._visibleView.$('#transcription').prop 'tagName'
console.log #appView._visibleView.$(':focus').prop 'tagName'
done()
#appView.mainMenuView.trigger 'request:formAdd'
Is there any way that I can write unit tests for these types of behaviors?
Ok, first off let me clarify something: the term "unit test" means man different things to many people. Often times it becomes synonymous with "any test written using a unit test framework (like Mocha)". When I use the term "unit test" that's not what I mean: what I mean is a test that tests only a single unit of work (which, in a JS environment, will usually be a single function, but might be a whole class).
Ok, with that out of the way, if you really are trying to unit test your code, you're sort of taking the wrong approach. A unit test really shouldn't rely on anything outside the context of the function being tested, and so relying on the (external) DOM is where your problem lies.
Let's assume your focus-handling code is in a function called handleFocus (I don't know the actual method name). Consider the following test, which I'll write using JavaScript since my CoffeScript is rusty:
describe('#handleFocus', function() {
it('remembers the currently focused element of a subview', function() {
var setFocusStub = sinon.stub($.fn, 'focus');
appView._visibleView.handleFocus();
expect(setFocusStub.calledOnce).to.be(true);
});
});
The above is a bit of an over-simplification, but hopefully it illustrates the point. What you're really trying to check isn't whether the DOM (fake or real) does X; what you're trying check is whether your function does X. By focusing on that in your test, and relying on a stub that checks whether "X" happened or not, you completely eliminate the need for the DOM to be involved.
Now of course you might wonder: "well great, that helps me in test-land, but how do I know it will work in a real environment?" My answer to that would be that your (probably Selenium-based) acceptance tests should cover that sort of thing. Acceptance tests should check whether your overall code works in the real world, while unit tests should ensure that individual pieces of that code work in a fake environment.
The former is great for ensuring your customers don't see bugs, while the latter is great for figuring out exactly where those bugs come from, and for making it possible for you to refactor safely.

Test class instanciation within a method using a spy in Javascript (Jasmine/Mocha)

I am currently starting to test my javascipt code and I now have a problem I am not able to solve. I have a Backbone App (AMD/requirejs driven) and I use Mocha (Sinon, Chai, ...) for BDD testing - this basically wraps up my setup.
Let's say we are talking about this class
class MyApp extends App
init: ->
#initcontrollers()
initControllers: ->
new HeaderController()
new NavController()
To the the first method init, I can write the following testcase
before ...
describe 'init', ->
it 'should call #initControllers', ->
spy = sinon.spy(#myInstance, 'initControllers')
#myInstance.init()
expect(spy.called).toBeTruthy()
this works pretty good. but now I'd like to test, if the second method initControllers actually creates new instances of HeaderController and NavController
How can I achieved that? I am stuck with that right now and I am a little bit confused because I start thinking of it not to be the right way to call those controllers.
Any help appreciated
I was really confused, but #mu-is-to-short probably gave me the right hint
I did it like this now:
describe '#initControllers', ->
it 'should call HeaderController', ->
headerController = new HeaderController()
spy = sinon.spy(headerController.__proto__, 'initialize')
#myInstance.initControllers()
expect(spy.calledOnce).toByTruthy()
It works for me, would that be the right approach?
Thanks anyway

Categories

Resources