测试中的异常行为:node.js/ Mocha

Unusual behavior in testing: node.js/ Mocha

我最近开始使用 nodeJS 并开始使用 Express & Wagnor 构建一个中间件应用程序

下面是我的package.JSON

{
"devDependencies": {
  "gulp": "3.8.11",
  "gulp-mocha": "2.0.1",
  "mocha": "2.2.4",
  "superagent": "1.2.0",
  "wagner-core": "0.1.0"
 },
  "dependencies": {
    "underscore": "1.5.2",
  "mongodb": "2.2.10",
  "express": "",
  "http-status": "0.1.8",
  "mongoose":""
 },
 "scripts": {
 "test": "mocha test.js"
 }
 }

我陷入了一种非常特殊的情况,搜索了类似的事件报告,但找不到任何相关的信息。我确信我犯了一个愚蠢的错误,需要帮助来识别它。

这是场景,我有一个带有 6 个端点的基本中间件设置,我正在使用 mocha 进行测试。

我有一个包含 6 个测试的 test.js 文件,问题是当我 运行 它通过一次并再次失败然后再次通过而没有对环境或代码或测试进行任何干预。这让我非常担心我的应用程序在生产环境中的行为是否相似,或者它是一个测试相关问题或一个已知的 mocha 问题(找不到任何问题)。

下面是我的测试文件

var URL_ROOT = 'http://localhost:3131';
var express = require('express');
var wagner = require('wagner-core');
var assert = require('assert');
var superagent = require('superagent');
var mongoose = require('mongoose');
//mongoose.set('debug', true);

var PRODUCT_ID = '0000000000000000000001';

describe('Store API', function(){
    var server; 
    var Product;
    var Catagory;
    var User;


    before(function() {
        var app = express();

        // Bootstrap sertver
        models = require('./models')(wagner);
        require('./dependencies')(wagner);
        app.use(require('./api')(wagner));
        wagner.invoke(require('./auth'), {app: app});
        server = app.listen(3131);

        //Make Catagory and Product model available in test
        Catagory = models.Catagory;
        Product = models.Product;
        User = models.User;


        app.use( function(req, res, next) {
          User.findOne({}, function(error, user) {
               console.log('Time:', Date.now());
               assert.ifError(error);
               req.user = user;
               next();
           });  //user.findone
       }); //app.use
    }); //before

    after(function(){
        //Shut the server
        server.close();
    }); //after

    beforeEach(function(done) {
        //Make sure Catagories are empty before each test
        Catagory.remove({}, function(error) {
            assert.ifError(error);
            Product.remove({}, function(error) {
                assert.ifError(error);
                User.remove({}, function(error) {
                    assert.ifError(error);
                    //Create Products, Catagories and Users
                    var catagories = [
                        { _id: 'Electronics'},
                        { _id: 'Phones', parent: 'Electronics'},
                        { _id: 'Laptop', parent: 'Electronics'},
                        { _id: 'Meat'}
                    ];

                    // create product data
                    var products = [
                        {
                            _id: '00000000000001',
                            name: 'LG G4',
                            catagory: { _id: 'Phones', ancestors: ['Electronics', 'Phones']},
                            price: {
                                amount: 300,
                                currency: 'USD'
                            }
                        },
                        {
                            _id: '00000000000002',
                            name: 'Asus Zenbook Prime',
                            catagory: { _id: 'Laptop', ancestors: ['Electronics', 'Laptop']},
                            price: {
                                amount: 2000,
                                currency: 'USD'
                            }
                        },
                        {
                            _id: '00000000000003',
                            name: 'MeatOne Goasht Wala',
                            catagory: {_id: 'Meat', ancestors: ['Meat']},
                            price: {
                                amount: 20,
                                currency: 'USD'
                            }
                        }
                    ];

                    var users = [{
                        profile: {
                            username: 'shoaibhb',
                            picture: 'http://pbs.twimg.com/profile_images/364903575/ShoaibHayat_Butt.jpg'
                        },
                        data: {
                            oauth: 'invalid',
                            cart: []
                        }
                    }];

                    Catagory.create( catagories, function(error) {
                        assert.ifError(error)
                        Product.create(products, function(error) {
                            assert.ifError(error)
                              User.create(users, function(error) {
                                  assert.ifError(error);
                                  User.findOne({}, function(err, user) {

                                  });
                             });
                        });
                    });
                });
                done();
            });
        });
    });

    it('can load a Catagory by id', function(done) {
        // Create a single Catagory
        // Catagory.create({ _id: 'Electronics' }, function(error, doc) {
        //     assert.ifError(error);
            var url = URL_ROOT + '/catagory/id/Electronics';
            //Make and HTTP request to localhost:3131/catagory/id/Electronics
            superagent.get(url, function(error, res) {
                assert.ifError(error);
                var result;
                // and make sure we got { _id: 'Electronics'} back
                assert.doesNotThrow(function() {
                    result = JSON.parse(res.text);
                });
                assert.ok(result.catagory);
                assert.equal(result.catagory._id, 'Electronics');
                done();
            });
        //});// ends here
    });

    it('can load all Catagories that have a certain parent', function(done) {

        // Create 4 catagories was here earlier, moved to beforeEach()

        //Catagory.create(catagories, function(error, catagories) {
            var url = URL_ROOT + '/catagory/parent/Electronics';
            //make HTTP request to /catagory/parent/Electronics
            superagent.get(url, function(error, res) {
                assert.ifError(error);
                var result;
                assert.doesNotThrow(function() {
                    result = JSON.parse(res.text);
                });
                assert.equal(result.catagories.length, 2);
                // should be in assending order by _id
                assert.equal(result.catagories[0]._id, 'Laptop');
                assert.equal(result.catagories[1]._id, 'Phones');
                done();
            });
        //});// create cat ends here
    });

    it('can load a product by id', function(done) {
        //Create a single product


        var oneProduct = {
                name: 'LG G4',
                _id: PRODUCT_ID,
                catagory: { _id: 'Phones', ancestors: ['Electronics', 'Phones']},
                price: {
                    amount: 300,
                    currency: 'USD'
                }
            };

        Product.create(oneProduct, function(error, doc) {
          assert.ifError( error);
            var url = URL_ROOT + '/product/id/' + PRODUCT_ID;
            // Make HTTP request to 
            // localhost:3131/product/id/0000000000000000000001
            superagent.get(url, function(error, res) {
                assert.ifError(error);
                var result = {};
                //And make sure we got LG G4 back

                assert.doesNotThrow(function() {
                    result = JSON.parse(res.text);
                });

                assert.ok(result.product);
                assert.equal(result.product._id, PRODUCT_ID);
                assert.equal(result.product.name, 'LG G4');
                done();
            });     
        });
    });

    it('can load all products in a Catagory with sub-catagories', function(done) {

        // moved to beforeEach function 

                var url = URL_ROOT + '/product/catagory/Electronics';
                //Make HTTP Request to loca:3131
                superagent.get(url, function(error, res) {
                    assert.ifError(error);
                    var result;
                    assert.doesNotThrow(function() {
                        result = JSON.parse(res.text);                
                    });
                    assert.equal(result.product.length, 2);
                    // should be assending order by name
                    assert.equal(result.product[0].name, 'Asus Zenbook Prime');
                    assert.equal(result.product[1].name, 'LG G4');

                    //Sort by price , assending
                    var url = URL_ROOT + '/product/catagory/Electronics?price=1';
                    superagent.get(url, function(error, res) {
                        assert.ifError(error);
                        var result;
                        assert.doesNotThrow(function() {
                            result = JSON.parse(res.text);
                        });
                        assert.equal(result.product[0].name, 'LG G4');
                        assert.equal(result.product[1].name, 'Asus Zenbook Prime');
                    });
                    //console.log(error);
                    assert.ifError(error);
                    done();
            });
    });


    it('can load user cart', function(done) {
        var url = URL_ROOT + '/me/';
        User.findOne({}, function( error, user) {
            console.log("error %j",error);
            //assert.ifError(error);
            user.data.cart = [{ product: PRODUCT_ID, quantity: 1}];
            console.log(user.data.cart);
            user.save(function(error) {
                assert.ifError(error);
                superagent.get(url, function(error, res) {
                    assert.ifError(error);
                    assert.equal(res.status, 200);
                    var result;
                    assert.doesNotThrow(function() {
                        result = JSON.parse(res.text).user;
                    });
                    assert.equal(result.data.cart.length,1);
                    assert.equal(result.data.cart[0].product.name, 'Asus Zenbook Prime');
                    assert.equal(result.data.cart[0].quantity, 1);

                });
            });
        });
        done();
    });



    it('can save user cart', function(done) {
        var url = URL_ROOT + '/me/cart/';

        superagent.put(url).send({
             data: {
                 cart: [{ product: PRODUCT_ID, quantity: 1}]
             }
         }).end(function(error, res) {
             assert.ifError(error);
             assert.equal(res.status, status.OK);
             User.findOne({}, function(error, user) {
                 assert.ifError(error);
                 assert.equal(user.data.cart.length, 1);
                 assert.equal(user.data.cart[0].product, PRODUCT_ID);
                 assert.equal(user.data.cart[0], quantity, 1);
             });
         });
         done();
    });
    enter code here

});

当我 运行 这些测试时,会发生以下情况: a) 6 个测试通过 b) 一些测试失败 c) 有空白响应

D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js

D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (81ms)
    √ can load all Catagories that have a certain parent
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    1) "before each" hook


  5 passing (752ms)
  1 failing

  1) Store API "before each" hook:
     Uncaught TypeError: Cannot read property 'data' of null
      at test-6passing.js:243:17
      at node_modules\mongoose\lib\query.js:1173:16
      at node_modules\mongoose\node_modules\kareem\index.js:109:16




D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (89ms)
    √ can load all Catagories that have a certain parent (38ms)
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    1) "before each" hook


  5 passing (922ms)
  1 failing

  1) Store API "before each" hook:
     Uncaught TypeError: Cannot read property 'data' of null
      at test-6passing.js:243:17
      at node_modules\mongoose\lib\query.js:1173:16
      at node_modules\mongoose\node_modules\kareem\index.js:109:16




D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (96ms)
    √ can load all Catagories that have a certain parent
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    √ can save user cart


  6 passing (852ms)


D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js

D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (80ms)
    √ can load all Catagories that have a certain parent
    √ can load a product by id
    1) can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    2) "before each" hook


  4 passing (862ms)
  2 failing

  1) Store API can load all products in a Catagory with sub-catagories:

      Uncaught AssertionError: 0 == 2
      + expected - actual

      -0
      +2

      at test-6passing.js:215:28
      at Request.callback (node_modules\superagent\lib\node\index.js:785:12)
      at IncomingMessage.<anonymous> (node_modules\superagent\lib\node\index.js:
990:12)
      at endReadableNT (_stream_readable.js:913:12)

  2) Store API "before each" hook:
     Uncaught TypeError: Cannot read property 'data' of null
      at test-6passing.js:243:17
      at node_modules\mongoose\lib\query.js:1173:16
      at node_modules\mongoose\node_modules\kareem\index.js:109:16




D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (86ms)
    √ can load all Catagories that have a certain parent (38ms)
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    1) "before each" hook


  5 passing (744ms)
  1 failing

  1) Store API "before each" hook:
     Uncaught TypeError: Cannot read property 'data' of null
      at test-6passing.js:243:17
      at node_modules\mongoose\lib\query.js:1173:16
      at node_modules\mongoose\node_modules\kareem\index.js:109:16




D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (97ms)
    √ can load all Catagories that have a certain parent (43ms)
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    √ can save user cart


  6 passing (785ms)


D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (83ms)
    √ can load all Catagories that have a certain parent
    √ can load a product by id (39ms)
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    1) "before each" hook


  5 passing (797ms)
  1 failing

  1) Store API "before each" hook:
     Uncaught TypeError: Cannot read property 'data' of null
      at test-6passing.js:243:17
      at node_modules\mongoose\lib\query.js:1173:16
      at node_modules\mongoose\node_modules\kareem\index.js:109:16




D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js

D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>mocha test-6passing.js


  Store API
express-session deprecated undefined resave option; provide resave option auth.j
s:57:39
express-session deprecated undefined saveUninitialized option; provide saveUnini
tialized option auth.js:57:39
    √ can load a Catagory by id (93ms)
    √ can load all Catagories that have a certain parent (38ms)
    √ can load a product by id
    √ can load all products in a Catagory with sub-catagories
    √ can load user cart
error null
    √ can save user cart


  6 passing (806ms)


D:\Programs_Insalled\nodejs\finalMEAN\edx\retailStore>

所以问题:

  1. 我做错了吗
  2. 我的做法不对吗
  3. mocha 中是否存在我不知道的问题
  4. 就是这样

我的环境是 MongoDB,Node,Express,Windows

编辑:完整代码可以在这里找到:

https://github.com/shoaibhb/retailStore

所以,这里确实没有足够的信息,但我可以告诉您要查找的内容。让我们从您遇到的错误开始:

Uncaught TypeError: Cannot read property 'data' of null
  at test-6passing.js:243:17
  at node_modules\mongoose\lib\query.js:1173:16
  at node_modules\mongoose\node_modules\kareem\index.js:109:16

所以,第二行告诉我们错误是在 test-6passing.js 中抛出的,在第 243 行,第 17 列。所以,不幸的是,SO 没有显示行号,所以我没有确切知道它在哪里,但您可以在文本编辑器中找到该行,它会告诉您错误发生时正在执行哪个函数。

第一行告诉我们您正在尝试读取对象的 'data' 属性,但该对象是 null。假设第 243 行第 17 列的函数是 Catagory.remove-- 我们可以查看该函数以准确了解您的测试中 null 是什么对象。然后,您必须查看 为什么 null

这有无数可能的原因。也许您在 Catagory.remove 函数中拼错了对象名称?也许信息还没有填充--javascript 是一种异步语言,对于许多来自其他编程语言的人来说很难习惯。

如果这没有帮助,请尝试使用定义模型及其 methods/functions 的代码更新您的 post。

看起来您的测试并没有让数据库每次都处于相同状态。错误在这里:

it('can load user cart', function(done) {
    var url = URL_ROOT + '/me/';
    User.findOne({}, function( error, user) {
        console.log("error %j",error);
        //assert.ifError(error);
        user.data.cart = [{ product: PRODUCT_ID, quantity: 1}];

其中 user 为空。也许一些测试正在删除一个用户而另一个正在添加它?

这我不会称之为问题的解决方案,但它解决了我的问题。我通过 beforeEach() 函数更改为在每个测试蚂蚁稳定它之前单独处理数据的创建和删除。

这是我所做的代码。

 beforeEach(function(done) {
    // Make sure categories are empty before each test
    Category.remove({}, function(error) {
      assert.ifError(error);
      Product.remove({}, function(error) {
        assert.ifError(error);
        User.remove({}, function(error) {
          assert.ifError(error);
          done();
        });
      });
    });
  });

  beforeEach(function(done) {
    var categories = [
      { _id: 'Electronics' },
      { _id: 'Phones', 'parent': 'Electronics' },
      { _id: 'Laptops', 'parent': 'Electronics' },
      { _id: 'Bacon' }
    ];

    var products = [
      {
        name: 'LG G4',
        category: { _id: 'Phones', ancestors: ['Electronics', 'Phones'] },
        price: {
          amount: 300,
          currency: 'USD'
        }
      },
      {
        _id: PRODUCT_ID,
        name: 'Asus Zenbook Prime',
        category: { _id: 'Laptops', ancestors: ['Electronics', 'Laptops'] },
        price: {
          amount: 2000,
          currency: 'USD'
        }
      },
      {
        name: 'Flying Pigs Farm Pasture Raised Pork Bacon',
        category: { _id: 'Bacon', ancestors: ['Bacon'] },
        price: {
          amount: 20,
          currency: 'USD'
        }
      }
    ];

    var users = [{
      profile: {
        username: 'vkarpov15',
        picture: 'http://pbs.twimg.com/profile_images/550304223036854272/Wwmwuh2t.png'
      },
      data: {
        oauth: 'invalid',
        cart: []
      }
    }];

    Category.create(categories, function(error) {
      assert.ifError(error);
      Product.create(products, function(error) {
        assert.ifError(error);
        User.create(users, function(error) {
          assert.ifError(error);
          done();
        });
      });
    });
  });