Как юнит-тестировать просмотры в ember.js?

Мы находимся в процессе изучения Ember.js. Мы делаем всю нашу разработку TDD и хотим, чтобы Ember.js не был исключением. У нас есть опыт создания приложений Backbone.js на основе тестирования, поэтому мы знакомы с тестированием внешнего кода с использованием Jasmine или Mocha/Chai.

При выяснении того, как тестировать представления, мы столкнулись с проблемой, когда шаблон для использования представления содержит оператор #linkTo. К сожалению, мы не можем найти хорошие тестовые примеры и практики. Суть в том, что мы пытаемся получить ответы на вопросы о том, как прилично проводить модульное тестирование Ember-приложений.

Глядя на тест для linkTo в Ember .js, мы заметили, что он содержит полную проводку приложения ember для поддержки #linkTo. Означает ли это, что мы не можем заглушить это поведение при тестировании шаблона?

Как вы создаете тесты для ember-представлений с помощью рендеринга шаблонов?

Вот суть с нашим тестом и шаблон, который сделает тест успешным, и шаблон, который заставит его потерпеть неудачу.

view_spec.js.coffee

# This test is made with Mocha / Chai,
# With the 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') is a div that gets cleaned between each test
      Em.run => @view.appendTo '#konacha'

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

    it 'is livebound', ->
      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: 'Moar todo'
          @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>

person THAiSi    schedule 15.02.2013    source источник
comment
В основном у вас будут проблемы с созданием экземпляров в изоляции. Ember хочет, чтобы все ваше приложение работало. Это не ответ, но в целом я предпочитаю интеграционные тесты (см. это видео) над модульными тестами. Я видел, как люди пытаются тестировать представления изолированно, и даже когда это работает, это не кажется хорошей стратегией. См. slideshare.net/jo_liss/testing-ember-apps/27. для некоторых из обоснований этого.   -  person Jo Liss    schedule 17.02.2013


Ответы (2)


Наше решение состояло в том, чтобы загрузить все приложение целиком, но максимально изолировать испытуемых. Например,

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("renders the foo's favoriteFood", function() {
    this.foo.set('favoriteFood', 'ramen');
    Em.run.sync();
    expect( this.subject.$().text() ).toMatch( /ramen/ );
  });
});

То есть маршрутизатор и другие глобалы доступны, так что это не полная изоляция, но мы можем легко отправить двойники для вещей, находящихся ближе к тестируемому объекту.

Если вы действительно хотите изолировать маршрутизатор, помощник linkTo ищет его как controller.router, так что вы можете сделать

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

this.subject = App.FooView.create({
  controller: { router: this.router },
  foo: this.foo
});
person James A. Rosen    schedule 17.02.2013

Один из способов справиться с этим — создать заглушку для хелпера linkTo, а затем использовать ее в блоке before. Это позволит обойти все дополнительные требования реального linkTo (например, маршрутизацию) и позволит вам сосредоточиться на содержимом представления. Вот как я это делаю:

// Test helpers
TEST.stubLinkToHelper = 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.restoreLinkToHelper = function() {
    Ember.Handlebars.helpers['link-to'] = TEST.originalLinkToHelper;
    TEST.originalLinkToHelper = null;
};

// Foo test
describe('FooView', function() {
    before(function() {
        TEST.stubLinkToHelper();
    });

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

    it('renders the favoriteFood', function() {
        var view = App.FooView.create({
            context: {
                foo: {
                    favoriteFood: 'ramen'
                }
            }
        });

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

        expect(view.$().text()).to.contain('ramen');
    });
});
person Scott Nedderman    schedule 19.11.2013