Emberjs测试在Phantomjs中首次失败



问题

我有一个/login路由,该路由使用ember-simple-auth实现身份验证。在测试过程中,ember-cli-mirage用于模拟后端。用户通过提供其电子邮件地址和密码来登录。

我总共有4项对此路线的接受测试,类似于以下测试:

test('should show error message for invalid email', function(assert) {
  visit('/login');
  fillIn('input#email', 'invalid-email');
  fillIn('input#password', 'invalid-password');
  click('button.button');
  andThen(function() {
    assert.equal(find('div.notification').text(), "Invalid email/password");
  });
});

当我使用ember t运行测试时,仅文件中的第一个测试失败。如果我评论此测试,下一个测试将失败,依此类推。如果我使用ember t -s以服务器模式运行测试,则相同的测试失败;但是,当我按 Enter 重新进行测试时,所有测试都通过了。

失败消息始终相同,如下所示:

not ok 7 PhantomJS 2.1 - Acceptance | login: should show error message for invalid email
    ---
        actual: >
        expected: >
            Invalid email/password
        stack: >
            http://localhost:7357/assets/tests.js:22:19
            andThen@http://localhost:7357/assets/vendor.js:48231:41
            http://localhost:7357/assets/vendor.js:48174:24
            isolate@http://localhost:7357/assets/vendor.js:49302:30
            http://localhost:7357/assets/vendor.js:49258:23
            tryCatch@http://localhost:7357/assets/vendor.js:68726:20
            invokeCallback@http://localhost:7357/assets/vendor.js:68738:21
            publish@http://localhost:7357/assets/vendor.js:68709:21
            http://localhost:7357/assets/vendor.js:48192:24
            invoke@http://localhost:7357/assets/vendor.js:10892:18
            flush@http://localhost:7357/assets/vendor.js:10960:15
            flush@http://localhost:7357/assets/vendor.js:11084:20
            end@http://localhost:7357/assets/vendor.js:11154:28
            run@http://localhost:7357/assets/vendor.js:11277:19
            run@http://localhost:7357/assets/vendor.js:32073:32
            http://localhost:7357/assets/vendor.js:48783:24
        Log: |

进行了所有测试,测试排放出例外:

# tests 60
# pass  59
# skip  0
# fail  1
Not all tests passed.
Error: Not all tests passed.
    at EventEmitter.getExitCode (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/lib/app.js:434:15)
    at EventEmitter.exit (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/lib/app.js:189:23)
    at /home/jon/projects/jonblack/wishlist-web/node_modules/testem/lib/app.js:103:14
    at tryCatcher (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/promise.js:510:31)
    at Promise._settlePromise (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/promise.js:567:18)
    at Promise._settlePromise0 (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/promise.js:612:10)
    at Promise._settlePromises (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/promise.js:691:18)
    at Async._drainQueue (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/async.js:138:16)
    at Async._drainQueues (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/async.js:148:10)
    at Immediate.Async.drainQueues (/home/jon/projects/jonblack/wishlist-web/node_modules/testem/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:637:20)
    at tryOnImmediate (timers.js:610:5)
    at processImmediate [as _immediateCallback] (timers.js:582:5)

这似乎是为了进行测试失败而不仅仅报告测试失败,因此也许是奇怪的,所以也许是相关的。

在Firefox和Chromium工作中运行测试,在开发模式下运行应用程序并手动登录。问题仅限于phantomjs。

我还有另一条路线的其他接受测试,所有这些都通过了。它似乎仅限于/login路线,这表明它可能与身份验证有关。

调试

我尝试通过将pauseTest()添加到测试中,将"phantomjs_debug_port": 9000添加到testem.js中,但是当我使用调试控制台时,Firefox和Chromium都无能为力。这可能是我缺乏调试phantomjs的经验,但我至少希望它会给我一个错误 - 从字面上看,它没有任何作用。

在我的ember应用中,phantomjs和某些东西之间的时机似乎存在时机问题。

我并不是说调试phantomjs问题或Ember接受测试失败的经验,因此任何帮助都将受到赞赏。

版本

ember-cli 2.10.0
ember-simple-auth 1.1.0
ember-cli-mirage 0.2.4

更新1

该按钮在login-form组件中:

<form {{action 'login' on='submit'}}>
  <p class="control has-icon">
    {{input value=email id='email' placeholder='email' class='input'}}
    <i class="fa fa-envelope"></i>
  </p>
  <p class="control has-icon">
    {{input value=password id='password' placeholder='password'
            type='password' class='input'}}
    <i class="fa fa-lock"></i>
  </p>
  <p class="control">
  <button class="button is-success" disabled={{isDisabled}}>Log In</button>
  </p>
</form>

组件的登录操作只会调用登录处理程序中传递的登录操作:

import Ember from 'ember';
export default Ember.Component.extend({
  email: "",
  password: "",
  isDisabled: Ember.computed('email', 'password', function() {
    return this.get('email') === "" || this.get('password') === "";
  }),
  actions: {
    login() {
      var email = this.get('email');
      var password = this.get('password');
      this.attrs.login(email, password);
    }
  }
});

是登录控制器中的authenticate方法:

import Ember from 'ember';
export default Ember.Controller.extend({
  session: Ember.inject.service(),
  actions: {
    authenticate(email, password) {
      this.get('session').authenticate('authenticator:oauth2', email, password).catch((data) => {
        this.set('errors', data['errors']);
      });
    }
  }
});

更新2

Daniel I建议的测试延迟:

test('should show error message for invalid email', function(assert) {
  visit('/login');
  fillIn('input#email', 'invalid-email');
  fillIn('input#password', 'invalid-password');
  click('button.button');
  andThen(function() {
    Ember.run.later(this, function() {
      assert.equal(find('div.notification').text(), "Invalid email/password");
    }, 0);
  });
});

仅使用Ember.run.later测试仍然失败,但是将其放置在andThen中会导致它通过。您是否注意到了奇异的部分?延迟为0毫秒。

我仍然想为此找到一个解释,因为我不相信这在测试上运行的任何机器上都会运行。

更新3

今天我感到惊讶:突然,测试再次工作!

我添加了一个带有接受测试的新路线。该路线本身是一条经过身份验证的路线,因此测试使用authenticateSession测试助手从Ember-Simple-auth进行身份验证。

当我删除使用此助手的测试时,错误返回!

我不确定这意味着什么。感觉就像是ember-simple-auth,但助手解决另一个时机问题也可能是一个巨大的巧合。

我们去的兔子洞...

更新4

以下是Ember-Cli-Mirage中验证端点的配置:

this.post('/token', function({db}, request) {
  var data = parsePostData(request.requestBody);
  if (data.grant_type === 'password') {
    // Lookup user in the mirage db
    var users = db.users.where({ email: data.username });
    if (users.length !== 1) {
      return new Mirage.Response(400, {'Content-Type': 'application/json'}, {
        errors: [{
          id: 'invalid_login',
          status: '400',
          title: 'Invalid email/password',
        }]
      });
    }
    var user = users[0];
    // Check password
    if (data.password === user.password) {
      if (!user.active) {
        return new Mirage.Response(400, {'Content-Type': 'application/json'}, {
          errors: [{
            id: 'inactive_user',
            status: '400',
            title: 'Inactive user',
          }]
        });
      } else {
        return new Mirage.Response(200, {
          'Content-Type': 'application/json'
        }, {
          access_token: 'secret token!',
          user_id: user.id
        });
      }
    } else {
      return new Mirage.Response(400, {'Content-Type': 'application/json'}, {
        errors: [{
          id: 'invalid_login',
          status: '400',
          title: 'Invalid email/password',
        }]
      });
    }
  } else {
    return new Mirage.Response(400, {'Content-Type': 'application/json'}, {
      errors: [{
        id: 'invalid_grant_type',
        status: '400',
        title: 'Invalid grant type',
      }]
    });
  }
});
this.post('/revoke', function(db, request) {
  var data = parsePostData(request.requestBody);
  if (data.token_type_hint === 'access_token' ||
      data.token_type_hint === 'refresh_token') {
    return new Mirage.Response(200, {'Content-Type': 'application/json'});
  } else {
    return new Mirage.Response(400, {'Content-Type': 'application/json'},
                               {error: 'unsupported_token_type'});
  }
});

更新5

这是我的config/environment.js文件:

/* jshint node: true */
module.exports = function(environment) {
  var ENV = {
    modulePrefix: 'wishlist-web',
    environment: environment,
    rootURL: '/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
      },
      EXTEND_PROTOTYPES: {
        // Prevent Ember Data from overriding Date.parse.
        Date: false
      }
    },
    APP: {
    }
  };
  if (environment === 'development') {
  }
  if (environment === 'test') {
    // Testem prefers this...
    ENV.locationType = 'none';
    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;
    ENV.APP.rootElement = '#ember-testing';
  }
  if (environment === 'production') {
    ENV.ServerTokenEndpoint = 'http://localhost:9292/token';
    ENV.ServerTokenRevocationEndpoint = 'http://localhost:9292/revoke';
    ENV.ApiHost = 'http://localhost:9292';
  }
  return ENV;
};

您在这里尝试调试此问题。

  1. 您可以从按钮中删除{{isDisabled}},以确保在尝试单击它时不会禁用它。

  2. 使用settimeout而不是andthen,看看是否是正时问题。

  3. 无需替换身份验证的操作代码,以确保它不会导致测试失败。

您也可以重写测试以在JavaScript中进行某些事件后将您的assert.ok放置。例如,您可以模拟身份验证的操作或观察者错误属性。您可以通过在接受环境中使用查找或寄存器来执行此操作 - 我的Ember CLI附加子中的测试可以帮助您-Ember-link-action/tests/cactance/link-action-action-test.js。

编辑

看到对您有用的经验告诉我,您应该尝试两件事。

对于此代码:

andThen(function() {
    Ember.run.later(this, function() {
      assert.equal(find('div.notification').text(), "Invalid email/password");
    }, 0);
  });

您可以尝试Ember.run.scheduleOnce('afterRender', this, () => { ... assert here }而不是使用Ember.run.later。或者,您可以尝试仅使用Ember.run而不是Ember.run.later

结论:解决此问题的关键可能是将您的断言放在Ember Run Loop中。

我会假设您看到的错误(Invalid email/password)是(模拟)服务器响应,并指示您在测试中使用的模拟或凭据是错误的。

我也不会使用Mirage嘲笑身份验证请求。Mirage(就像Jason API一样)是基于资源的,而不是非常适合身份验证的东西。

最新更新