Javascript 使用Ember(cli)如何获得验收测试以等待承诺?

Javascript 使用Ember(cli)如何获得验收测试以等待承诺?,javascript,ember.js,Javascript,Ember.js,在我的Ember应用程序中,我目前有一个模型,它有一个findResults函数,该函数返回一个承诺,即包装Google Places库以获取自动完成的结果。为了在我的UI中使用它,我设置了PromiseMixin控制器。我指示控制器观察searchText值,当该值发生变化时,我将控制器的承诺值更新为findResults函数返回的承诺,但使用searchText中的新值。当我在浏览器中玩应用程序时,这很好,但是当我运行验收测试时,测试似乎在承诺返回之前完成,因此测试失败。我将在下面包括相关文

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

我不知道如何告诉余烬在测试期间等待承诺的解决

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;
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')
});
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');
  });
});
app/controllers/index.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;
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')
});
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');
  });
});
测试/验收/创建新条目测试.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;
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')
});
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');
  });
});

我无法在中重现您的问题,但您是否尝试过
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();
   });
});

我对这种东西不熟悉,今天也遇到了类似的困难。我发现,
只会等待使用余烬测试承诺创建的承诺

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()
,以防止在调用第二个断言之前退出测试。我还需要在
运行中包装第二个断言。下一个
调用为属性/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();
    });
  });
});
test('在未解析的承诺解析为true时显示内容',函数(){
期望(2);
var承诺;
var testPromise=Ember.Test.promise(函数(解析){
resolveTestPromise=resolve;
});
//创建组件实例,保留安全服务和模板属性
var component=this.subject({
securityService:CreateMockSecurityService(testPromise),
模板:Ember.handlebar.compile(“”)
});
//将组件附加到页面
var$component=this.append();
//我们的潜水艇应该还看不见
相等($component.find('div#if may test div')。长度,0);
停止();
later(null,函数(){resolveTestPromise(true);},1000);
第四(函数){
Ember.run.next(函数(){
//div现在应该是可见的
相等($component.find('div#if may test div')。长度,1);
start();
});
});
});

希望有帮助

最好的方法是注册您自己的异步测试助手。我准备了一个JSBin,其中模拟了您的代码和解决方案:

用于创建辅助对象的代码如下所示:

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');
  });
});
test('访问/搜索',函数()){
期望(1);
访问("/");;
单击(“.位置输入”);
填写(“.位置输入”,“加利福尼亚州洛杉矶”);

waitForControllerWithPromise('index');//如果您正在等待一个不会解决但会拒绝的承诺,这里有一个补丁来捕获错误并仍然传递
余烬测试帮助程序

Ember.Test.registerAsyncHelper('WaitForControllerWithPromission',函数(应用程序,控制器名称){
返回新的Ember.Test.promise(函数(解析、拒绝){
//通知测试框架正在进行异步操作,
所以不应该考虑测试完成。
Ember.Test.adapter.asyncStart();
//抓住我们要守住的诺言
变量控制器=应用程序容器查找('controller:'+controllerName);
var promise=controller.get('promise');
promise.then(函数(){
//等待afterRender队列解决此承诺,
//给予承诺的任何副作用解决的机会
//发生并解决
Ember.run.schedule('afterRender',null,resolve);
//通知测试框架此异步操作已完成
Ember.Test.adapter.asyncEnd();
}).catch(函数(){
//当承诺被拒绝时,决心让它通过`
Ember.run.schedule('afterRender',null,resolve);
Ember.Test.adapter.asyncEnd();
});
});

})
我刚刚尝试了
start()
stop()
,但没有成功。如果我使用
stop()
start()将断言包装在setTimeout方法中
如果我给它足够的时间,它会工作的。我想如果我在我的承诺中调用Ember.run,Ember的测试助手会等待承诺解决。
Ember.test.promise
使用
stop()
start()完成了它
。我根本不记得在测试文档中看到过这一点,甚至不知道它的存在。很高兴你指出了这一点。这帮了大忙,谢谢!对我来说,在应用程序中添加代码只是为了满足测试要求似乎很不幸。这是因为它与外部服务交互吗?尽管我感到不舒服,但它确实修复了间歇性的错误我的测试中ace条件失败,谢谢。您对如何使用组件有什么建议吗?@BuckDoyle ember testing了解ember应用程序中的许多异步内容(例如,在运行循环中安排的项目、ajax操作和路由转换),但当您在应用程序中执行ember testing所知之外的操作时(如本问题所述),您必须找出某种方法来等待测试中的异步操作。Re:您的组件问题,我需要更多背景知识来回答该问题。我需要看得更多