Making responsive websites that resize and redesign themselves based on the user’s screen size has become standard, and there are many tools that help you do this with Ember applications. However, a common frustration is that this behavior is difficult to test automatically. Developers often write tests exclusively with the desktop screen width in mind, and run into two serious problems:

Bugs in the mobile app are not caught by the build. Tests may “randomly” fail on the developer’s computer because their browser window is too small.

There are some excellent Ember add-ons for building responsive applications such as ember-responsive from FreshBooks from and ember-screen by Mitch Lloyd. There’s been some discussion on the ember-responsive repo about testing strategies. Today I’m going to show you how abstracting your screen-size-detection into a service makes it easy to test different screen sizes and preemptively detect screen-size-related test failures.

I’d like to mention that Matthew J. Morrison has another interesting solution for Ember Applications. In this blog post he describes how, using Karma as your test runner, you can configure it to start up Chrome in different screen widths and then run each test suite with the appropriate configuration. Check it out, it’s really cool!

All the code that I’m going to use below is on Github at https://github.com/iezer/responsive-testing

A Simple App with a Navigation Bar

I’m going to start with a brand new Ember app, and add the ember-screen add-on.

ember new responsive-testing

ember install ember-screen

I’m going to define the Screen Service to have 2 breakpoints:

// app/services/screen.js import EmberScreen, { breakpoint } from 'ember-screen'; export const minDesktopWidth = 480; export default EmberScreen.extend({

isMobile: breakpoint(`(max-width: ${minDesktopWidth - 1}px)`),

isDesktop: breakpoint(`(min-width: ${minDesktopWidth}px)`)

});

and make a NavBar component which shows a navigation bar on Desktop and the obligatory hamburger on mobile.

// app/pods/components/nav-bar/component.js import Ember from 'ember';

const { Component, inject } = Ember; export default Component.extend({

screen: inject.service()

}); {{!-- app/pods/components/nav-bar/template.hbs --}} {{#if screen.isMobile}}

<span class='hamburger'>

☰

</span>

{{else}}

<span class='desktop-nav-bar'>Desktop Nav Bar</span>

{{/if}} {{!-- app/templates/application.hbs --}} <h2 id='title'>Welcome to Ember</h2>

{{nav-bar}}

{{outlet}}

Here’s a test for the component.

//tests/integration/pods/components/nav-bar/component-test.js import { moduleForComponent, test } from 'ember-qunit';

import hbs from 'htmlbars-inline-precompile'; moduleForComponent('nav-bar', 'Integration | Component | nav bar', {

integration: true

}); test('it renders', function(assert) {

this.render(hbs`{{nav-bar}}`);

assert.equal(this.$().text().trim(), 'Desktop Nav Bar');

});

Note that this test only tests the desktop version and the test will fail if you run the test file in Chrome and make your browser smaller than 480px. Try it out!

Testing with a Stubbed Service

As of Ember 2.1.0, Ember has a nice API for stubbing services in tests, so if we upgrade to Ember 2.2.0 in our bower.json file, we can stub the Screen Service and write a integration test for mobile users.

// tests/integration/pods/components/nav-bar/component-test.js import { moduleForComponent, test } from 'ember-qunit';

import hbs from 'htmlbars-inline-precompile';

import Ember from 'ember'; const screenServiceStub = Ember.Service.extend({

isMobile: false,

isDesktop: true

}); moduleForComponent('nav-bar', 'Integration | Component | nav bar', {

integration: true, beforeEach() {

this.register('service:screen', screenServiceStub);

this.inject.service(‘screen’);

}

}); test('it renders in desktop mode', function(assert) {

this.render(hbs`{{nav-bar}}`); assert.equal(this.$().text().trim(), 'Desktop Nav Bar');

}); test('it renders in mobile mode', function(assert) {

this.set('screen.isMobile', true); this.render(hbs`{{nav-bar}}`); assert.equal(this.$().text().trim(), '☰');

});

This is a great improvement! We are now able to test different browser widths. Also these tests will both pass no matter how large or small the browser window when the tests are running. Try it out!

I’ve seen some apps render both the desktop nav bar and mobile hamburger and then use CSS media queries to show and hide one or the other based on the window size. This cannot be tested using our stubbed-screen-service strategy. Rendering things to the DOM that you know are going to be invisible can also degrade performance, unless you need it for things like animated transitions. For these reasons I recommend not using CSS media queries for changing the behavior of the app and specifically for determining whether to show or hide an element. This should be done in the handlebars template using properties from the service so that you can easily stub the values.

Mitigating Responsive Test Issues

If you have an app that still has or requires media queries, there are still things you can do to mitigate hard-to-debug test failures. One is to explicitly assert at the top of your test that the screen size is correct. Here is an example acceptance test that does just that. It uses the real Screen Service instead of the stubbed one.

// tests/acceptance/index-test.js import { test } from 'qunit';

import moduleForAcceptance from 'responsive-testing/tests/helpers/module-for-acceptance';

import startApp from 'responsive-testing/tests/helpers/start-app';

import destroyApp from 'responsive-testing/tests/helpers/destroy-app'; let application;

let screenService; moduleForAcceptance('Acceptance | index', {

beforeEach() {

application = startApp(); // real ScreenService

screenService = application.__container__.lookup('service:screen');

}, afterEach() {

destroyApp(application);

}

}); test('visiting / shows nav bar', function(assert) {

visit('/'); andThen(function() {

assert.ok(screenService.get('isDesktop'), 'Browser must be desktop width for this test'); assert.equal(currentURL(), '/');

assert.equal(find('span.desktop-nav-bar').length, 1, 'Desktop nav bar visible');

});

});

If you find yourself needing to do this in all tests for a particular file or the whole app you can easily put the assertion in the beforeEach or make a test helper.

One last trick that is handy is to use Modernizr to check the screen width before the Ember App and the tests are even loaded. You can include Modernizr in your app by running

ember install ember-cli-modernizr

And then add this to your test-helper file

// tests/test-helper.js /* global QUnit */

import resolver from './helpers/resolver';

import {

setResolver

} from 'ember-qunit'; // we set this to 480 up above

import { minDesktopWidth } from '../services/screen'; QUnit.begin(() => {

let mqVal = window.Modernizr.mq(`(min-width: ${minDesktopWidth}px)`);

if (!mqVal) {

console.warn(`Acceptance tests require a minimum screen width of ${minDesktopWidth}px. Please expand the window and/or reduce the size of the dev tools.`);

}

}); setResolver(resolver);

I hope these tips will help you test responsiveness in your Ember apps. Remember that all the code is on Github.