Tips for testing views in ember.js using unit tests

We are currently delving into the world of Ember.js. Our development process is completely test-driven, and we aim to apply the same methodology to Ember.js. Having prior experience in test-driven development with Backbone.js apps using Jasmine or Mocha/Chai, we are familiar with testing front-end code.

However, when it comes to testing views that contain a #linkTo statement in the template, we encountered some challenges. We have been searching for good examples and best practices for testing such scenarios. This gist represents our journey to uncover effective ways to unit-test Ember applications.

Upon examining the linkTo test in the Ember.js source code, we observed a complete setup of an Ember app to support #linkTo. This raises the question of whether we can simulate this behavior when testing a template.

How can tests be set up for ember views that include template renders?

Check out this gist containing our test cases, along with a template that will pass the test and another that will fail it.

view_spec.js.coffee

# Written with Mocha / Chai,
# Utilizing chai-jquery and chai-changes extensions

describe 'TodoItemsView', ->

  beforeEach ->
    testSerializer = DS.JSONSerializer.create
      primaryKey: -> 'id'

    TestAdapter = DS.Adapter.extend
      serializer: testSerializer
    TestStore = DS.Store.extend
      revision: 11
      adapter: TestAdapter.create()

    TodoItem = DS.Model.extend
      title: DS.attr('string')

    store = TestStore.create()
    @todoItem = store.createRecord TodoItem
      title: 'Do something'

    @controller = Em.ArrayController.create
      content: []

    @view = Em.View.create
      templateName: 'working_template'
      controller: @controller

    @controller.pushObject @todoItem

  afterEach ->
    @view.destroy()
    @controller.destroy()
    @todoItem.destroy()

  describe 'amount of todos', ->

    beforeEach ->
      # $('#konacha') serves as a div that is reset between each test
      Em.run => @view.appendTo '#konacha'

    it 'is displayed', ->
      $('#konacha .todos-count').should.have.text '1 things to do'

    it 'is dynamically updated', ->
      expect(=> $('#konacha .todos-count').text()).to.change.from('1 things to do').to('2 things to do').when =>
        Em.run =>
          extraTodoItem = store.createRecord TodoItem,
            title: 'More tasks'
          @controller.pushObject extraTodoItem

broken_template.handlebars

<div class="todos-count"><span class="todos">{{length}}</span> things to do</div>

{{#linkTo "index"}}Home{{/linkTo}}

working_template.handlebars

<div class="todos-count"><span class="todos">{{length}}</span> things to do</div>

Answer №1

Our approach has been to load the entire application, but focus on isolating our test subjects as much as we can. For instance,

describe('FooView', function() {
  beforeEach(function() {
    this.foo = Ember.Object.create();
    this.subject = App.FooView.create({ foo: this.foo });
    this.subject.append();
  });

  afterEach(function() {
    this.subject && this.subject.remove();
  });

  it("displays the favorite food of 'foo'", function() {
    this.foo.set('favoriteFood', 'sushi');
    Em.run.sync();
    expect( this.subject.$().text() ).toMatch( /sushi/ );
  });
});

In this setup, the router and other global components are accessible, so it's not a total isolation, but we can easily substitute doubles for elements that are closely related to the object being tested.

If there is a need to fully isolate the router, the linkTo helper references it as controller.router, which allows you to do something like this:

this.router = {
  generate: jasmine.createSpy(...)
};

this.subject = App.FooView.create({
  controller: { router: this.router },
  foo: this.foo
});

Answer №2

To simplify testing, one approach is to create a mock for the linkTo helper and utilize it within a before block. This allows you to avoid dealing with the complexities associated with the actual linkTo function (such as routing) and instead concentrate on the view's content. Here's an example of how this can be implemented:

// Custom test helpers
TEST.createLinkToMock = function() {
    if (!TEST.originalLinkToHelper) {
        TEST.originalLinkToHelper = Ember.Handlebars.helpers['link-to'];
    }
    Ember.Handlebars.helpers['link-to'] = function(route) {
        var options = [].slice.call(arguments, -1)[0];
        return Ember.Handlebars.helpers.view.call(this, Em.View.extend({
            tagName: 'a',
            attributeBindings: ['href'],
            href: route
        }), options);
    };
};

TEST.restoreLinkToMock = function() {
    Ember.Handlebars.helpers['link-to'] = TEST.originalLinkToHelper;
    TEST.originalLinkToHelper = null;
};

// Sample test case for FooView
describe('FooView', function() {
    before(function() {
        TEST.createLinkToMock();
    });

    after(function() {
        TEST.restoreLinkToMock();
    });

    it('should display the favoriteFood property correctly', function() {
        var view = App.FooView.create({
            context: {
                foo: {
                    favoriteFood: 'sushi'
                }
            }
        });

        Em.run(function() {
            view.createElement();
        });

        expect(view.$().text()).to.contain('sushi');
    });
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilize a variable within an HTML attribute

Currently utilizing Angular and I have the following HTML snippet <input onKeyDown="if(this.value.length==12 && event.keyCode!=8) return false;"/> Is there a way for me to incorporate the variable "myNum" instead of the ...

What is the best way to fulfill promises sequentially?

I'm currently facing a challenge with organizing promises in the correct order. I am developing a chat bot for DiscordApp using Node.js and have extensively searched both on this platform and Google. Despite trying Promise.all and an Async function, I ...

Spinning Global Lighting and Bouncing in three.js

I am trying to create a 3D object that automatically rotates around the Y axis while still allowing users to scale and custom rotate the object with their mouse. This is what I have so far: import * as THREE from 'three'; import { GLTFLoader } f ...

Ways to isolate and limit the application of CSS in React and Gatsby to specific pages instead of having it affect the global scope

At the moment, I'm working on integrating a keen-slider library(https://www.npmjs.com/package/keen-slider). To install it, we have to include import 'keen-slider/keen-slider.min.css'. This essentially adds the CSS globally to our project. Ho ...

Refreshing the identification of a button

I have been working on updating an ID with a value from another button. Here is my current progress: $('.viewemployment').on('click', function(e){ var url = '<?php echo Config::get('URL'); ?>dashboard/employmen ...

Seamlessly replacing an image in PixiJS without any sudden movement

I'm currently developing a HTML5 JavaScript game with a heavily textured background. My goal is to incorporate a 3D background element that can be swapped out dynamically. For example, the initial scene may show a room with a closed door, but once a J ...

Using jQuery to store the last selected href/button in a cookie for easy retrieval

Recently, I've been working on a document that features a top navigation with 4 different links. Each time a link is clicked, a div right below it changes its size accordingly. My goal now is to implement the use of cookies to remember the last select ...

Troubleshooting a Problem with JSON Array in JavaScript/jQuery - Understanding Undefined Values

Currently, I am fetching data from a JSON file: [ { "video": "video/preroll" }, { "video": "video/areyoupopular" }, { "video": "video/destinationearth" }] I have attempted to use console.log at different stages and encountered an issue when p ...

The issue of Jquery ajax functionality not functioning properly within the Laravel 5.6 framework

Within the file assets/js/bootstrap.js, I currently have the following code: window._ = require('lodash'); window.Popper = require('popper.js/dist/umd/popper'); try { window.$ = window.jQuery = require('jquery/dist/jquery.sl ...

Is there a way for me to retrieve the current value of a star rating when I click on it?

Looking for assistance with a ReactJS rating stars component that behaves oddly. The starting value is set to 5 stars, but each click on the stars seems to return the previous value rather than the current one. import React, {useState} from "react&quo ...

Using jQuery to clear a textarea when a select input is changed

Currently, I am in the process of developing a small text editor that enables users to edit files and create new ones. Here is how I have set up my select menu. <select name="reportname" id="reportname" class="form-control"> <option value="zr ...

Enhancing JSON data in Datatables with additional text

I'm currently looking for a way to insert some text into my data before generating a table using jQuery DataTables. As an example, if my JSON data looks like [1,5,6,12], I would like it to be displayed as [1 seconds, 5 seconds, 6 seconds, 12 seconds] ...

CSS Grid expands the item width on its own when there is still space left

Having some queries regarding the piece of code provided. Currently, I have a collection of 5 cards displayed in rows of 3 cards each using the styling grid-template-columns: repeat(3, minmax(200px, 1fr));. My concern is whether there is a way for the 4th ...

Setting up an OnMouseOver event for each URL on a webpage

Is there a way to add an OnMouseOver event for all anchor tags on a page, without replacing any existing event handlers that are already in place? I'm looking for guidance on how to achieve this using JavaScript or JQuery. Any suggestions would be gr ...

Vue select is causing the selected choice of another selector to be influenced

Currently, I am facing an issue with a table displaying a specific Nova resource. The problem lies in the selectors within each row of the table - when one selector is changed, all other selectors automatically mirror that change. This behavior seems to be ...

Only carry out a redirect to the specified page if the successRedirect is present in the passport.authenticate function

Encountering some difficulties with a Node Express app. After removing the successRedirect property in the auth method by passport, it fails to redirect. The below code does not redirect to the desired page when the successRedirect is removed and replaced ...

Encountering an error while configuring webpack with ReactJS: Unexpected token found while

I'm attempting to update the state of all elements within an array in ReactJS, as illustrated below. As a newbie to this application development, it's challenging for me to identify the mistake in my code. closeState(){ this.state.itemList.f ...

Passing data through events from a parent component to a child component in Vue.js using the $emit method

Having trouble making this work! I have included the $emit code in my click event to trigger a custom event. <h2 class="form_section--header">Business Hours - <a href="javascript:void(0)" v-on:click="$emit('addBusinessHours', null)"> ...

Validating URL patterns in JavaScript using Ajax

Below are the ajax urls displayed in a specific format: http://example.com/v1/components/compId http://example.com/v1/machine/machineId http://example.com/v1/graph/startTime=value?endtime=value http://example.com/v1/graph/startDate=value?enddate=value? ...

The password-protected HTML blog remains hidden until the correct entry is entered into the password box, causing

After successfully creating a password redirect page where entering the correct password redirects you to another URL (psswrdtest.tumblr.com with the password as correctpsswrd, redirecting to google.com*), I attempted to improve it by making the password p ...