2016-02-28 150 views
1

完成NodeJS測試noob在這裏。嘗試單獨測試通過我的API調用的函數(意思是,而不是向特定的端點發出http請求,而這通常會調用幾個函數,然後向不同的第三方API發出請求),我想單獨測試函數)。他們被調用的方式是我爲每個數據源(數據源=第三方API)構建了一個類,每個類都包含具有相同確切簽名的相同函數 - getDataconvertData,並返回包含結果的回調。Nodejs - 摩卡,柴多個異步測試

我還創建用於創建許多user嘲笑,由於每個用戶上下文返回不同的數據的模塊(意思是,一個用戶對象被送入getData,其中爲了確定哪些數據應返回使用某些user特性) 。

我想測試的方式是創建大量模擬,然後爲每個模擬運行函數。這是我到目前爲止有:

// Data sources to iterate over. Each is a class instance acquired through "require". 
var dataSources = [ 
    source1, 
    source2, 
    source3, 
    source4 
]; 

describe('getData', function() {  
    this.timeout(10000); 
    describe('per data source,', function() { 
     context('standard call', function() { 

      // Associative array to hold the data returned, a key for each data source. 
      var finalResults = {}; 

      // Iterate over all data sources 
      _.forEach(dataSources, function(dataSource) { 

       // Generate user mocks 
       var users = userMocks(10); 

       // Iterate over all users. 
       _.forEach(users, function (user) { 

        // Call each data source with each of the users. 
        // Numbers of calls to make - (users * data-sources), so in this case - 10*4. 
        dataSource.getData(user, function (err, data) { 
         if (err) return done(err); 

         // Convert the data returned to my format 
         dataSource.convertData(data, function (err, processedData) { 
          if (err) return done(err); 

          // Populate finalResults with converted data from each source 
          if (finalResults[dataSource.sourceName]) { 
           finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData); 
          } else { 
           finalResults[dataSource.sourceName] = processedData; 
          } 
         }); 
        }); 
       }); 
      }); 

      it('should return something', function(done) { 
       _.forEach(finalResults.keys, function(key) { 
        expect(finalResults[key]).to.not.be.empty; 
        expect(finalResults[key].length).to.be.greaterThan(0); 
       }); 
       setTimeout(function() { 
        done(); 
       }, 10000); 
      }) 
     }); 
    }); 
}); 

});`

這工作(或至少測試通過查詢的時候是有效的,這是我想要的東西),但它的繁瑣和(非常)遠離優雅或有效,特別是使用超時而不是使用承諾,某種異步,或者我可能還不熟悉的另一種選擇。

由於大多數我發現的資源(http://alanhollis.com/node-js-testing-a-node-js-api-with-mocha-async-and-should/https://developmentnow.com/2015/02/05/make-your-node-js-api-bulletproof-how-to-test-with-mocha-chai-and-supertest/https://justinbellamy.com/testing-async-code-with-mocha/,只是僅舉幾例)討論了直接的API測試,而不是具體的異步功能的,我希望得到更多的經驗Noders一些輸入/最佳實踐技巧。

+0

在node.js中啓動並拆除http srevers是相當容易的。而不是數據嘲諷,創建大量輕量級「模擬」數據服務器,對請求數據執行測試,並嚮應用程序提供「模擬」數據。你甚至可以在服務器和測試代碼之間共享javascript執行上下文!這將使你的測試從「單元」到「集成」的規模稍微有所變化,但會讓這些事情變得更加清潔。 – lxe

+0

@lxe我完全同意,沒有涉及太多的細節,但這就是我基本上在做的,最終的結果是一個用戶數組,但我使用一個服務來做到這一點(正在使用其他地方)。接下來的步驟是測試我自己的API來鞏固所有這些第三方通話,這很像您所描述的。真的很感謝輸入! – CodeBender

回答

0

您需要知道何時完成一堆異步操作。來測試優雅的方式是使用承諾,並承諾聚集:

Promise.all([ promise1, promise2, promise3 ]).then(function(results) { 
    // all my promises are fulfilled here, and results is an array of results 
}); 

包裝你dataSources到使用bluebird一個承諾。你並不需要修改測試的代碼自我,藍鳥提供了方便的方法:

var Promise = require('bluebird') 
var dataSources = [ 
    source1, 
    source2, 
    source3, 
    source4 
].map(Promise.promisifyAll); 

使用新promisified功能,爲每個呼叫建立承諾:

context('standard call', function() { 
     var finalResults = {}; 
     var promiseOfResults = datasources.map(function(dataSource) { 
      var users = userMocks(10); 
      // Promise.all will take an array of promises and return a promise that is fulfilled then all of promises are 
      return Promise.all(users.map(function(user) { 
       // *Async functions are generated by bluebird, via Promise.promisifyAll 
       return dataSource.getDataAsync(user) 
        .then(dataSource.convertDataAsync) 
        .then(function(processedData) { 
         if (finalResults[dataSource.sourceName]) { 
          finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData); 
         } else { 
          finalResults[dataSource.sourceName] = processedData; 
         } 
        }); 
      }); 
     }); 
     // promiseOfResults consists now of array of agregated promises 
     it('should return something', function(done) { 
      // Promise.all agregates all od your 'datasource' promises and is fulfilled when all of them are 
      // You don't need the promise result here, since you agegated finalResults yourself 
      return Promise.all(promiseOfResults).then(function() { 
       _.forEach(finalResults.keys, function(key) { 
        expect(finalResults[key]).to.not.be.empty; 
        expect(finalResults[key].length).to.be.greaterThan(0); 
       }); 
       done(); 
      }); 
     }); 

測試的其餘部分應使用相同Promise.all(promiseOfResults),除非你需要新的結果。

+0

請原諒缺乏經驗,但是如果函數已經構建爲使用回調函數,那麼這很重要嗎?傳遞一個簡單的錯誤處理函數是否會滿足承諾? – CodeBender

+0

@CodeBender藍鳥的promisification需要它的函數/對象有'callback(err,result)',所以是的,它確實很重要。換句話說,如果未構建'getData',則不會生成'getDataAsync',而是使用回調。測試代碼無需承諾就可以做出測試代碼,但它會顯着變大並且不易讀。 – Koder

+0

我不認爲我的問題已經足夠清楚了 - 不確定getDataAsync和convertDataAsync是從哪裏來的 - 您能否詳細說明這些問題?這只是我的'getData'和'convertData'函數的一個佔位符,或者是自動生成的實際函數,或者是應該添加到每個數據源類的函數? – CodeBender