您可以在请求回调中嵌套 it() 块吗?

Can you nest it() blocks in a request callback?

您似乎可以将 "stuff" 放在 describe 内,但放在 it 外:

describe('1', function() {
  var foo = 'bar';
  console.log(foo);
  it('1a', function(done) {
    expect(true).toBe(true);
    done()
  });
  it('1b', function(done) {
    expect(true).toBe(true);
    done();
  });
});

而且您似乎可以将 it 块放在一个名为的函数中:

describe('1', function() {
  (function() {
    it('1a', function(done) {
      expect(true).toBe(true);
      done()
    });
    it('1b', function(done) {
      expect(true).toBe(true);
      done();
    });
  })()
});

两种情况下的输出都是:

~/code/study-buddies $ jasmine-node server
bar
..

Finished in 0.005 seconds
2 tests, 2 assertions, 0 failures, 0 skipped

但是当我尝试将我的 it 块放入节点 request 回调中时,它似乎没有 运行 测试:

describe('1', function cb1() {
  var foo = 'bar';
  console.log(foo);
  var request = require('request');
  request.get('http://localhost:3000/api/posts', function() {
    console.log('here');
    it('1a', function cb1a(done) {
      console.log('1a');
      expect(true).toBe(true);
      done();
    });
    it('1b', function cb1b(done) {
      console.log('1b');
      expect(true).toBe(true);
      done();
    });
  });
});

输出:

~/code/study-buddies $ jasmine-node server
bar


Finished in 0.007 seconds
0 tests, 0 assertions, 0 failures, 0 skipped


here

当我运行我的真实代码:

var request = require('request');
var base_url = 'http://localhost:3000/api/posts';

describe('Posts', function() {
  var post;

  beforeEach(function(done) {
    var options = {
      url: base_url,
      method: 'POST',
      json: {
        title: 'one'
      }
    };
    request(options, function(err, response, body) {
      if (err) console.log('ERROR IN THE SET UP.');
      else { post = body; }
      done();
    });  
  });

  afterEach(function(done) {
    request.del(base_url+'/'+post._id, function(err, response, body) {
      if (err) console.log('ERROR IN THE TEAR DOWN.');
      else { post = null; }
    });
    done();
  });
  describe('GET /', function() {
    it('returns a status code of 200', function(done) {
      request.get(base_url, function(err, response, body) {
        expect(response.statusCode).toBe(200);
        done();
      });
    });
    it('returns the right posts', function(done) {
      request.get(base_url, function(err, response, body) {
        expect(JSON.parse(body)).toEqual([post]);
        done();
      });
    });
  });
  describe('GET /:id', function() {
    it('returns a status code of 200', function(done) {
      request.get(base_url+'/'+post._id, function(err, response, body) {
        expect(response.statusCode).toBe(200);
        done();
      });
    });
    it('returns the right post', function(done) {
      request.get(base_url+'/'+post._id, function(err, response, body) {
        expect(JSON.parse(body)).toEqual(post);
        done();
      });
    });
  });
  describe('POST /', function() {
    var options = {
      url: base_url,
      method: 'POST',
      json: {
        title: 'two'
      }
    };

    request(options, function(err, response, body) {
      it('returns a status code of 201', function(done) {
        expect(response.statusCode).toBe(201);
        done();
      });
      it('returns the created Post', function(done) {
        expect(body).toEqual({title: 'two'});
        done();
      });
    });
  });
  describe('PUT /:id', function() {

  });
  describe('DELETE /:id', function() {

  });
});

我得到这个没有输出:

~/code/study-buddies $ jasmine-node server
~/code/study-buddies $

这是为什么?

注意:我试图将 it 块嵌套在 request 下,因为两个 it 块都在发出相同的请求,所以我想保持干爽。

这里有两个步骤:

  1. 收集测试(调用 it())。
  2. 执行收集的测试。

在您的第一个示例中,您的测试已定义,同时处理步骤 1。在您的第二个示例(带回调)中,您调用 it 是在步骤 2 期间执行的,因此 Jasmine 不处理它们。

如何处理

您需要定义单独的测试或仅在回调中使用对 expect() 的调用而不调用 it().

如果您需要将请求发送到相同的路由,但使用不同的参数,通常您希望定义单独的测试。典型的例子是用有效和无效数据测试 API 行为:

describe('POST /users/', function() {
    it('should create user', function (done) {
        request.post('/users/', { /* valid data */ }, function () { /* expect clauses */ });
    });

    it('should respond with errors given invalid data', function (done) {
        request.post('/users/', { /* invalid data */ }, function () { /* expect clauses */ });
    });
});

当您想测试单个请求的不同部分时,您想在单个测试中使用多个 expect() 语句。例如检查响应代码和几个参数的值:

describe('GET /users/{id}/', function() {
    it('should return user', function (done) {
        request.get('/users/14/', function (err, response, body) { 
            expect(response.code).toBe(200);
            expect(body.username).toBe('test');
            expect(body.email).toBe('test@example.com');
        });
    });
});

显然有一些介于两者之间的案例,这取决于您决定每个具体案例是否重要到足以放入单独的测试(并重复相同的请求)或可以通过额外的处理 expect() 单个测试中的语句。

深入讲解

这需要一些来自reader的背景知识:

  • 了解synchronous & asynchronous
  • 之间的区别
  • 了解 Node.js 中的 event loop 及其工作原理
  • 了解接受另一个函数的函数不需要异步(使用事件循环进行回调)(例如 forEach

继续之前的一些事实:

  • Jasmine 希望同步定义所有测试用例。在文件中调用 main describe() 完成后,它开始执行收集的测试。
  • describe()it() 的调用是同步的,而 HTTP 请求的执行是异步的(使用事件循环)。
  • describe() 为测试创建命名空间并同步调用作为第二个参数提供的函数。 it() 添加它的第二个参数作为对当前命名空间的测试。

让我们追踪您的第一个案例:

  1. 调用 describe() 创建命名空间 1 并同步执行 cb1().
  2. 调用 it() 同步添加测试 1a
  3. 调用 it() 同步添加测试 1b
  4. cb1() 的执行以及测试收集步骤已完成。
  5. 开始执行收集的测试。
  6. 执行测试。
  7. 打印结果。
  8. 退出程序。

添加 IIFE 不会改变任何东西,因为它只是同步调用自身。

我们来追踪你的第二个案例:

  1. 调用 describe() 创建命名空间 1 并同步执行 cb1().
  2. 发送请求,将回调放入事件循环队列(还记得 HTTP 请求是异步的吗?)并在下一行继续执行。
  3. cb1() 的执行以及测试收集步骤已完成。
  4. 开始执行收集的测试。
  5. 由于没有收集到测试,打印执行了 0 个测试。
  6. 当前调用栈执行完毕。所以从事件循环队列中取出下一个函数,也就是请求回调。
  7. 调用 it() 尝试添加测试 1a,但测试的执行已经完成。
  8. 退出程序。

此时应该清楚您不能也不应该在异步回调中定义测试。所以你的第三个例子永远不应该写。并且永远不要问为什么它在没有任何输出的情况下完成。


但出于兴趣,我查看了 Jasmine 源代码并进行了一些调试以查看实际发生的情况。给你。

您可能注意到函数 describe()it() 不是您导入的,而是由 Jasmine 自己提供的。除了为您导入这些函数之外,Jasmine 还提供了一些内部状态,供 it()describe() 用来收集测试。导入测试时,此内部状态已设置,但 运行 未设置。

当您从请求回调中调用 it() 时,您是在 运行 测试阶段执行此操作,并且未设置内部集。所以它失败并出现 Error: jasmine.Suite() required 错误。此错误导致 Jasmine 立即退出而没有任何输出。

你可能会问为什么第二个例子打印结果呢?这很简单:在您的第二个示例中,您没有任何其他测试,因此在调用 it() 时结果已经打印出来。您可以通过在对 it() 的调用之间添加另一个 console.log() 调用来检查它(它永远不会被打印)。