【问题标题】:Using Ember (cli) how do I get an acceptance test to wait for a promise?使用 Ember (cli) 如何获得验收测试以等待承诺?
【发布时间】:2014-10-22 01:03:23
【问题描述】:

在我的 Ember 应用程序中,我目前有一个模型,该模型具有 findResults 函数,该函数返回一个包装 Google Places 库以获取自动完成结果的承诺。为了在我的 UI 中使用它,我设置了一个 PromiseMixin 控制器。我指示控制器观察searchText 值,当它发生变化时,我将控制器的承诺值更新为findResults 函数返回的承诺,但使用来自searchText 的新值。当我在浏览器中使用应用程序时,这很有效,但是当我运行验收测试时,测试似乎在返回承诺之前完成,因此测试失败。我将在下面包含相关文件。

我不确定如何告诉 Ember 在测试期间等待承诺解决。

app/services/google-autocomplete-location.js

import Ember from "ember";

var googleAutocompleteLocation = Ember.Object.extend({
  placeId: null,
  description: null
});

googleAutocompleteLocation.reopenClass({
  findResults: function(query) {
    var self = this;
    var promise = new Ember.RSVP.Promise(function(resolve, reject) {
      var autocompleteService = new google.maps.places.AutocompleteService();

      return autocompleteService.getPlacePredictions({ input: query },
        function(predictions, status) {
          if (status !== google.maps.places.PlacesServiceStatus.OK) {
            Ember.run(null, reject, status);
          }
          else {
            Ember.run(null, resolve, self._decorateGoogleResults(predictions));
          }
        });
    });

    return promise;
  },

  _decorateGoogleResults: function(predictions) {
    var locations = [];

    predictions.forEach(function(prediction) {
      locations.push(
        googleAutocompleteLocation.create({
          placeId: prediction.place_id,
          description: prediction.description
        })
      );
    });


    return locations;
   }
});

export default googleAutocompleteLocation;

app/controllers/index.js

import Ember from "ember";
import GoogleLocation from "../services/google-location";
import GoogleAutocompleteLocation from '../services/google-autocomplete-location';

export default Ember.ArrayController.extend(Ember.PromiseProxyMixin, {
  searchText: '',
  map: null,
  mapUrl: null,

  actions: {
    submit: function() {
      return this.transitionToRoute('entries.new');
    }
  },

  highlightedResult: function() {
    if (this.get('model').length) {
      return this.get('model')[0];
    } else {
      return null;
    }
  }.property('model'),

  setMap: (function() {
    if (this.get('highlightedResult') === null) {
      return this.set('map', null);
    } else {
      if (this.get('map') === null) {
        return this.set('map', GoogleLocation.create({
          mapContainer: Ember.$('.maps-info'),
          placeId: this.get('highlightedResult').placeId
        }));
      } else {
        return this.get('map').set('placeId', this.get('highlightedResult').placeId);
      }
    }
  }).observes('highlightedResult'),

  searchTextChanged: (function() {
    if (this.get('searchText').length) {
      this.set('promise',
        GoogleAutocompleteLocation.findResults(this.get('searchText')));
      console.log(this.get('promise'));
    } else {
      this.set('model', []);
    }
  }).observes('searchText')
});

测试/接受/create-new-entry-test.js

test('finding a location', function() {
  expect(1);
  visit('/');
  click('.location-input input');
  fillIn('.location-input input', "Los Angeles, CA");

  andThen(function() {
    var searchResult = find('.search-results ul li:first a').text();

    equal(searchResult, 'Los Angeles, CA, United States');
  });
});

【问题讨论】:

标签: javascript ember.js


【解决方案1】:

解决此问题的最佳方法可能是注册您自己的异步测试助手。我已经准备了一个 JSBin 模拟你的代码和一个解决方案:http://jsbin.com/ziceratana/3/edit?html,js,output

用于创建助手的代码是这样的:

Ember.Test.registerAsyncHelper('waitForControllerWithPromise', function(app, controllerName) {
  return new Ember.Test.promise(function(resolve) {

    // inform the test framework that there is an async operation in progress,
    // so it shouldn't consider the test complete
    Ember.Test.adapter.asyncStart();

    // get a handle to the promise we want to wait on
    var controller = app.__container__.lookup('controller:' + controllerName);
    var promise = controller.get('promise');

    promise.then(function(){

      // wait until the afterRender queue to resolve this promise,
      // to give any side effects of the promise resolving a chance to
      // occur and settle
      Ember.run.schedule('afterRender', null, resolve);

      // inform the test framework that this async operation is complete
      Ember.Test.adapter.asyncEnd();
    });
  });
});

它会像这样使用:

test('visiting / and searching', function() {
  expect(1);
  visit('/');
  click('.location-input input');
  fillIn('.location-input input', "Los Angeles, CA");
  waitForControllerWithPromise('index'); // <-- simple & elegant!
  andThen(function(){
    var searchResult = find('.search-results ul li:first').text();
    equal(searchResult, 'Los Angeles, CA, United States');
  });
});

在 ember-testing 中,async-helper 将自动等待先前的 Promise,而后续的 async helper 将在执行测试时等待它。有关这方面的出色背景,请参阅 Cory Forsyth 的 Demystifying Async Testing

【讨论】:

  • 为了满足测试而向应用程序添加代码对我来说似乎很不幸。这是因为它是与外部服务的交互吗?尽管我感到不舒服,但它确实修复了我测试中间歇性的比赛条件失败,所以谢谢。
  • 您对如何使用组件有什么建议吗?
  • @BuckDoyle ember-testing 知道 Ember 应用程序中的许多异步事物(例如,在运行循环中安排的项目、ajax 操作和路由转换),但是当您在应用程序中执行某些操作时ember-testing 知道(如在这个问题中),您必须找出一些方法来等待测试中的异步操作。回复:您的组件问题,我需要更多背景才能回答这个问题。
  • 我需要更多地研究 ember-testing,因为我没有使用外部服务,它是 Ember Data .save(),所以我一定做错了什么。关于组件,我意识到我当前的设置违反了“数据向下,动作向上”,所以一旦我解决了这个问题,我就可以使用相同的控制器解决方案。但是一旦控制器消失......?!感谢您的回复!
  • 非常感谢这个解决方法和解释,卢克!发现这种行为我感到非常沮丧,甚至向 ember 提出了关于它的问题。你介意在那里插话吗?您认为这是一个问题,还是您认为这是一种合理的行为? github.com/emberjs/ember.js/issues/10578
【解决方案2】:

我是这个东西的新手,今天也遇到了类似的困难。我发现andThen 只会等待使用 Ember Test 承诺创建的承诺,

var promise = Ember.Test.promise(function (resolve, reject) {...});

而不是那些直接实例化承诺的地方,即

var promise = new Ember.RSVP.Promise(function (resolve, reject) {...});

Ember.Test.promise 返回一个new Ember.RSVP.Promise,但在返回它之前还做了将Ember.Test.lastPromise 设置为promise 实例的步骤。也许这里的答案是让您将Ember.Test.lastPromise 设置为您正在等待的承诺?

顺便说一句,在我的情况下,我还必须使用 stop() 和 start() 来防止测试在调用第二个断言之前退出。我还需要将第二个断言包装在 run.next 调用中,以使属性/DOM 有机会更新:

test('shows content when unresolved promise resolves true', function() {
  expect(2);

  var resolveTestPromise;
  var testPromise = Ember.Test.promise(function (resolve) {
    resolveTestPromise = resolve;
  });

  // creates the component instance, stubbing the security service and template properties
  var component = this.subject({
    securityService: CreateMockSecurityService(testPromise),
    template: Ember.Handlebars.compile('<div id="if-may-test-div" />')
  });

  // appends the component to the page
  var $component = this.append();

  // our div shouldn't be visible yet
  equal($component.find('div#if-may-test-div').length, 0);

  stop();
  Ember.run.later(null, function () {resolveTestPromise(true);}, 1000);

  andThen(function () {
    Ember.run.next(function () {
      // div should be visible now
      equal($component.find('div#if-may-test-div').length, 1);
      start();
    });
  });
});

希望有帮助!

【讨论】:

  • Ember.Test.promise 用stop() 和start() 做到了。我完全不记得在测试文档中看到过,甚至不知道它存在。很高兴你指出了这一点。大大的帮助,谢谢!
【解决方案3】:

我无法在 JSBin 中重现您的问题,但您是否尝试过 stop() 和 start()。在你的情况下:

test('finding a location', function() {
  expect(1);
  stop();
  visit('/')
   .click('.location-input input')
   .fillIn('.location-input input', "Los Angeles, CA")
   .then(function() {
     var searchResult = find('.search-results ul li:first a').text();
     equal(searchResult, 'Los Angeles, CA, United States');
     start();
   });
});

【讨论】:

  • 我刚刚尝试过start() 和stop(),但没有成功。如果我用 stop() 和 start() 将我的断言包装在 setTimeout 方法中,它会起作用,但是如果我给它足够的时间。我想如果我在我的 Promise 中调用 Ember.run,Ember 的测试助手会等待 Promise 解决。
【解决方案4】:

如果您正在等待一个不会解决但拒绝的承诺,这里有一个补丁可以捕获错误并仍然传递 andThen ember 测试助手。

Ember.Test.registerAsyncHelper('waitForControllerWithPromise', function(app, controllerName) {
  return new Ember.Test.promise(function(resolve, reject) {

    // inform the test framework that there is an async operation in progress,
    // so it shouldn't consider the test complete
    Ember.Test.adapter.asyncStart();

    // get a handle to the promise we want to wait on
    var controller = app.__container__.lookup('controller:' + controllerName);
    var promise = controller.get('promise');

    promise.then(function(){
      // wait until the afterRender queue to resolve this promise,
      // to give any side effects of the promise resolving a chance to
      // occur and settle
      Ember.run.schedule('afterRender', null, resolve);

      // inform the test framework that this async operation is complete
      Ember.Test.adapter.asyncEnd();
    }).catch(function() {
      // when the promise is rejected, resolve it to pass in `andThen()`
      Ember.run.schedule('afterRender', null, resolve);
      Ember.Test.adapter.asyncEnd();
    });
  });
});

【讨论】:

    猜你喜欢
    • 2023-04-03
    • 2015-05-14
    • 2017-09-14
    • 1970-01-01
    • 2020-10-17
    • 2015-10-25
    • 2017-09-18
    • 1970-01-01
    • 2021-02-02
    相关资源
    最近更新 更多