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

Ways to clear TextField status

My question is about a Textfield. In the case where the state is null but the text field value is showing in the Textfield. <TextField style={{ width: '65%'}} id="standard-search" ...

Swap out periods with commas in the content of Json Data

I have a JSON file containing percentage data that I am extracting and displaying on my website: <?php $resultData = file_get_contents('https://example.com/json/stats?_l=en'); $jsonData = json_decode($resultData, true); if( isset( ...

The Next.js API endpoint is struggling to process cross-domain POST requests

Dealing with a POST request in my NextJS application has been challenging. This particular post request is originating from a different domain. To address this issue, I included a receptor.ts file in the /pages/api directory: import { NextApiRequest, Next ...

Is there a way for me to attach a link to a picture displayed in a lightbox that directs to an external ".html" file?

I have a platform where users can view images in a gallery and when they click on an image, it opens up in a lightbox. I am trying to add a feature where the opened image can be clicked again to redirect the user to an external page. I attempted to nest t ...

"Learn how to capture the complete URL and seamlessly transfer it to another JavaScript page using Node.js and Express.js

Is there a way to access the token variable in another page (api.js)? I need to use it in my index.js. var express = require('express'); var router = express.Router(); router.get('/', function(req, res ...

What is the best way to dynamically adjust the select option?

I need help with handling JSON objects: [ { id: "IYQss7JM8LS4lXHV6twn", address: "US", orderStatus: "On the way", }, ]; My goal is to create a select option for the order status. If the current status is "On ...

The jQuery method .on gathers and retains click events

I created a component that manages a view containing articles with games. In order to prevent memory overload and optimize performance, I implemented a solution where when a user clicks on an article (each having the class "flashgame"), they can choose to ...

The dropdown menu in AngularJS is unable to retrieve the selected index

Presently, I have a dropdown menu: <select class="form-control" name="timeSlot" ng-model="user.dateTimeSlot" ng-change="dateTimeChanged(user.dateTimeSlot)" ng-blur="blur29=true" required style="float: none; margin: 0 auto;"> ...

At runtime, the array inexplicably becomes null

Having recently ventured into the world of Ionic framework development, I have encountered a puzzling issue. At runtime, an array inexplicably gets nulled and I am struggling to pinpoint the root cause. export interface Days { name:string; } @Compon ...

A guide to selecting the bookmark with a URL that is on a currently opened page

To gain a clearer understanding of my goal, follow these steps: Open the Chrome Browser and go to a URL, such as https://www.google.com. Once the page loads, locate and click on the bookmark labeled "ABC", which contains the URL ({window.open('/ ...

Utilizing Jquery for precise element placement and retrieving its position details

Within my bundle of jQuery code, there are a few areas where I am experiencing difficulties trying to recall functions. The following is an excerpt of the code: $(document).ready(function(){ $("#myTablePager").html(""); $.ajax({ type: "POS ...

Working with XML files in Node.js

I'm currently running an Arch Linux system with KDE plasma and I have a 50mb XML file that I need to parse. This XML file contains custom tags. Here is an example of the XML: <JMdict> <entry> <ent_seq>1000000</ent_seq&g ...

[Vue alert]: Issue with rendering: "TypeError: Unable to access property 'replace' of an undefined value"

I'm currently working on a project similar to HackerNews and encountering the following issue: vue.esm.js?efeb:591 [Vue warn]: Error in render: "TypeError: Cannot read property 'replace' of undefined" found in ---> <Item ...

Using DataTable with backend processing

Has anyone successfully implemented pagination in DataTable to dynamically generate the lengthMenu (Showing 1 to 10 of 57 entries) and only load data when the next page is clicked? I'm currently facing this challenge and would appreciate some guidance ...

Issue with highlighting when one string overlaps with another

I am facing a challenge with handling a string that contains Lorem Ipsum text. I have JSON data that specifies the start and end offsets of certain sections within the text that I need to highlight. The approach I am currently using involves sorting the JS ...

What is the best way to eliminate or substitute Unicode Characters in Node.js 16?

Currently, I have a file that is being read into a JSON Object. { "city": "Delicias", "address": "FRANCISCO DOMÍN\u0002GUEZ 9" } We are using this address to send it to the Google Maps API in order to ...

Discovering the total number of tickets based on priority in an array with Javascript

I have the following data set { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"Medium", task_id:T1 } { agent_id:002, priority:"High", task_id:T1 ...

Animate CSS during page load

Currently, I am implementing AJAX to dynamically load pages on my website. During the loading process, I wish to incorporate a spinning animation on the logo to indicate that content is being fetched. The jQuery script (although I'm still learning an ...

Divider displayed between images in Internet Explorer 8

On my website, I have arranged four images in a square using the code below: <div id="tempo_main"> <div id="tempo_content"> <div style="text-align: center;z-index: 3;position: absolute;right:350px; left:350px; t ...

Parent window login portal

I have just started learning how to program web applications, so I am not familiar with all the technical terms yet. I want to create a login window that behaves like this: When a user clicks on the Login button, a window should pop up on the same page t ...