node mocha_使用Mocha和Chai测试Node RESTful API
node mocha
介紹 ( Introduction )
I still remember the satisfaction of being finally able to write the backend part of a bigger app in node and I am sure many of you do it too.
我仍然記得能夠最終在節點中編寫更大的應用程序的后端部分感到滿意??,并且我相信你們中的許多人也可以這樣做。
And then? We need to make sure our app behaves the way we expect and one of the strongly suggested methodologies is software testing. Software testing is crazily useful whenever a new feature is added to the system: Having the test environment already set up which can be run with a single command helps to figure out whether a new feature introduces new bugs.
然后? 我們需要確保我們的應用按照預期的方式運行,強烈建議的方法之一是軟件測試。 每當將新功能添加到系統時,軟件測試就非常有用:已經設置了可以通過單個命令運行的測試環境,有助于弄清新功能是否引入了新的錯誤。
In the past, we've worked on building a RESTful Node API and authenticating a Node API.
過去,我們一直在努力構建RESTful Node API和對Node API進行身份驗證 。
In this tutorial we are going to write a simple RESTful API with Node.js and use Mocha and Chai to write tests against it. We will test CRUD operations on a bookstore.
在本教程中,我們將使用Node.js編寫一個簡單的RESTful API,并使用Mocha和Chai對其進行測試。 我們將在書店中測試CRUD操作。
As usual you can build the app step-by-step throughout the tutorial or directly get it on github.
像往常一樣,您可以在整個教程中逐步構建應用程序,也可以直接在github上獲取它。
摩卡:測試環境 ( Mocha: Testing Environment )
Mocha is a javascript framework for Node.js which allows Asynchronous testing. Let's say it provides the environment in which we can use our favorite assertion libraries to test the code.
Mocha是用于Node.jsJavaScript框架,該框架允許異步測試。 假設它提供了一個環境,我們可以在其中使用我們喜歡的斷言庫來測試代碼。
.Mocha comes with tons of great features, the website shows a long list but here are the ones I like the most:
摩卡(Mocha)具有許多強大的功能,該網站顯示了很長的列表,但以下是我最喜歡的列表:
- simple async support, including promises. 簡單的異步支持,包括承諾。
- async test timeout support. 異步測試超時支持。
- before, after, before each, after each hooks (very useful to clean the environment where each test!). 在每個鉤子之前,之后,之前,之后(對于清潔每個測試所在的環境非常有用!)。
- use any assertion library you want, Chai in our tutorial. 使用我們想要的任何斷言庫,Chai在我們的教程中。
柴:斷言圖書館 ( Chai: Assertion Library )
So with Mocha we actually have the environment for making our tests but how do we do test HTTP calls for example? Moreover, How do we test whether a GET request is actually returning the JSON file we are expective, given a defined input? We need an assertion library, that's why mocha is not enough.
因此,實際上,有了Mocha,我們就有了進行測試的環境,但是例如,我們如何測試HTTP調用呢? 此外,在給定定義的輸入的情況下,我們如何測試GET請求是否實際上返回了我們期望的JSON文件? 我們需要一個斷言庫,這就是為什么mocha不夠用的原因。
So here it is Chai, the assertion library for the current tutorial:
因此,這里是Chai ,是本教程的斷言庫:
@media (max-width: 1280px) { .go-go-gadget-react img:first-child { display: none; } }@media (max-width: 780px) {.go-go-gadget-react { flex-direction: column; }.go-go-gadget-react img { margin-left: 0 !important; margin-bottom: 12px !important; }.header-thingy { margin-top: 20px; }.button-thingy { margin-left: 0 !important; margin-top: 12px !important; }} @media (max-width: 1280px) { .go-go-gadget-react img:first-child { display: none; } }@media (max-width: 780px) {.go-go-gadget-react { flex-direction: column; }.go-go-gadget-react img { margin-left: 0 !important; margin-bottom: 12px !important; }.header-thingy { margin-top: 20px; }.button-thingy { margin-left: 0 !important; margin-top: 12px !important; }}Chai shines on the freedom of choosing the interface we prefer: "should", "expect", "assert" they are all available. I personally use should but you are free to check it out the API and switch to the others two. Lastly Chai HTTP addon allows Chai library to easily use assertions on HTTP requests which suits our needs.
Chai贊揚選擇我們喜歡的界面的自由:“應該”,“期望”,“聲明”它們都可用。 我個人使用應該,但是您可以自由地檢查它的API并切換到其他兩個。 最后, Chai HTTP插件允許Chai庫輕松使用適合我們需求的HTTP請求斷言。
先決條件 (Prerequisites)
- Node.js: a basic understanding of node.js and is recommended as i wont go too much into detail on building a RESTful API. Node.js :對node.js的基本了解,因此建議您這樣做,因為我不會在構建RESTful API時過分詳細。
- POSTMAN for making fast HTTP requests to the API. POSTMAN,用于向API發出快速HTTP請求。
- ES6 syntax: I decided to use the latest version of Node (6.*.*) which has the highest integration of ES6 features for better code readibility. If you are not familiar with ES6 you can take a look at the great scotch articles (Pt.1 , Pt.2 and Pt.3) about it but do not worry I am going to spend a few words whenever we encount some "exotic" syntax or declaration. ES6語法 :我決定使用最新版本的Node(6。*。*),該版本具有ES6功能的最高集成度,以提高代碼可讀性。 如果您不熟悉ES6,則可以查看有關它的出色的蘇格蘭文章( Pt.1 , Pt.2和Pt.3 ),但是不用擔心,每當我們遇到一些“異國情調”時,我都會花幾句話語法或聲明。
Time to setup our Bookstore!
是時候設置我們的書店了!
項目設置 ( Project setup )
目錄結構 (Directory Structure)
Here is the project directory for our API, something you must have seen before:
這是我們API的項目目錄,您之前必須已經看過它:
-- controllers ---- models ------ book.js ---- routes ------ book.js -- config ---- default.json ---- dev.json ---- test.json --test ---- book.js package.json server.jsonNotice the /config folder containing 3 JSON files: As the name suggests, they contain particular configurations for a specific purpose.
請注意/config文件夾,其中包含3個JSON文件:顧名思義,它們包含用于特定目的的特定配置。
In this tutorial we are going to switch between two databases, one for development and one for testing purposes, thus the files contain the mongodb URI in JSON format:
在本教程中,我們將在兩個數據庫之間切換,一個用于開發,一個用于測試,因此文件包含JSON格式的mongodb URI:
dev.json and default.json:
```javascript { "DBHost": "YOUR_DB_URI" } ```dev.json和default.json :
javascript {“ DBHost”:“ YOUR_DB_URI”}```test.json:
```javascript { "DBHost": "YOUR_TEST_DB_URI" } ```test.json :
javascript {“ DBHost”:“ YOUR_TEST_DB_URI”}```NB: default.json is optional however let me highlight that files in the config directory are loaded starting from it. For more information about the configuration files (config directory, file order, file format etc.) check out this link.
注意 : default.json是可選的,但是讓我強調一下config目錄中的文件是從該目錄開始加載的。 有關配置文件(配置目錄,文件順序,文件格式等)的更多信息,請查看此鏈接 。
Finally, notice /test/book.js, that's where we are going to write our tests!
最后,注意/test/book.js ,這就是我們要編寫測試的地方!
Package.json (Package.json)
Create the package.json file and paste the following code:
創建package.json文件并粘貼以下代碼:
{"name": "bookstore","version": "1.0.0","description": "A bookstore API","main": "server.js","author": "Sam","license": "ISC","dependencies": {"body-parser": "^1.15.1","config": "^1.20.1","express": "^4.13.4","mongoose": "^4.4.15","morgan": "^1.7.0"},"devDependencies": {"chai": "^3.5.0","chai-http": "^2.0.1","mocha": "^2.4.5"},"scripts": {"start": "SET NODE_ENV=dev && node server.js","test": "mocha --timeout 10000"} }Again the configuration should not surprise anyone who wrote more than a server with node.js, the test-related packages mocha, chai, chai-http are saved in the dev-dependencies (flag --save-dev from command line) while the scripts property allows for two different ways of running the server.
同樣,配置不應該讓任何使用node.js編寫服務器的人感到驚訝,與測試相關的軟件包mocha , chai , chai-http被保存在dev-dependencies(命令行中的--save-dev標志)中,而scripts屬性允許運行服務器的兩種不同方式。
To run mocha I added the flag --timeout 10000 because I fetch data from a database hosted on mongolab so the default 2 seconds may not be enough.
為了運行mocha,我添加了--timeout 10000標志,因為我從mongolab上托管的數據庫中獲取數據,因此默認的2秒可能還不夠。
Congrats! You made it through the boring part of the tutorial, now it is time to write the server and test it.
恭喜! 您已經完成了教程的無聊部分,現在該編寫服務器并對其進行測試了。
服務器 ( The server )
主要 (Main)
Let's create the file server.js in the root of the project and paste the following code:
讓我們在項目的根目錄中創建文件server.js并粘貼以下代碼:
let express = require('express'); let app = express(); let mongoose = require('mongoose'); let morgan = require('morgan'); let bodyParser = require('body-parser'); let port = 8080; let book = require('./app/routes/book'); let config = require('config'); //we load the db location from the JSON files //db options let options = { server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } } }; //db connection mongoose.connect(config.DBHost, options); let db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:'));//don't show the log when it is test if(config.util.getEnv('NODE_ENV') !== 'test') {//use morgan to log at command lineapp.use(morgan('combined')); //'combined' outputs the Apache style LOGs }//parse application/json and look for raw text app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.text()); app.use(bodyParser.json({ type: 'application/json'})); app.get("/", (req, res) => res.json({message: "Welcome to our Bookstore!"}));app.route("/book").get(book.getBooks).post(book.postBook); app.route("/book/:id").get(book.getBook).delete(book.deleteBook).put(book.updateBook);app.listen(port); console.log("Listening on port " + port);module.exports = app; // for testingHere are the key concepts:
以下是關鍵概念:
- We require the module config to access the configuration file named as the NODE_ENV content to get the mongo db URI parameter for the db connection. This helps us to keep the "real" database clean by testing on another database hidden to our app future users. 我們要求模塊config訪問名為NODE_ENV內容的配置文件,以獲取數據庫連接的mongo db URI參數。 這有助于我們通過對應用程序未來用戶隱藏的另一個數據庫進行測試來保持“真實”數據庫的清潔。
- The enviroment variable NODE_ENV is test against test to disable morgan log in the command line or it would interfere with the test output. 對環境變量NODE_ENV進行了針對測試的測試,以禁用命令行中的morgan日志,否則它將干擾測試輸出。
- The last line of code exports the server for testing purposes. 最后一行代碼導出服務器以進行測試。
- Notice the variables definition using let which makes the variable enclosed to the nearest enclosing block or global if outside any block. 注意使用let的變量定義,該變量使變量包含在最接近的封閉塊中,或者如果在任何塊外部,則為全局變量。
The remaining lines of codes are nothing new, we simply go through requiring all the necessary modules, define the header options for the communication with the server, craete the specific roots and eventually let the server listen on a defined port.
剩下的代碼行并不是什么新鮮的事情,我們只需完成所有必需的模塊,定義與服務器進行通信的標頭選項,創建特定的根目錄并最終讓服務器在定義的端口上進行偵聽就可以了。
模型和路線 (Model and Routes)
Time for our book model! Create a file in /app/model/ called book.js and paste the following code:
時間到了我們的圖書模型! 在/app/model/創建一個名為book.js的文件,并粘貼以下代碼:
let mongoose = require('mongoose'); let Schema = mongoose.Schema;//book schema definition let BookSchema = new Schema({title: { type: String, required: true },author: { type: String, required: true },year: { type: Number, required: true },pages: { type: Number, required: true, min: 1 },createdAt: { type: Date, default: Date.now }, }, { versionKey: false} );// Sets the createdAt parameter equal to the current time BookSchema.pre('save', next => {now = new Date();if(!this.createdAt) {this.createdAt = now;}next(); });//Exports the BookSchema for use elsewhere. module.exports = mongoose.model('book', BookSchema);Our book schema has a title, author, the number of pages, the publication year and the date of creation in the db. I set the versionKey to false since it's useless for the purpose of the tutorial.
我們的圖書模式在數據庫中具有標題,作者,頁數,出版年份和創建日期。 我將versionKey設置為false,因為對于本教程而言,它是無用的。
NB: the exotic callback syntax in the .pre() function is an arrow function, a function who has a shorter syntax which, according to the definiton on MDN , "lexically binds the this value (does not bind its own this, arguments, super, or new.target). Arrow functions are always anonymous".
注意 : .pre()函數中的奇異回調語法是一個箭頭函數,該函數具有較短的語法,根據MDN的定義,該函數“按詞法綁定此值(不綁定其自身的this,參數,超級或new.target)。箭頭函數始終是匿名的” 。
Well, pretty much all we need to know about the model so let's move to the routes.
好吧,我們幾乎需要了解有關該模型的所有知識,所以讓我們開始研究路線。
in /app/routes/ create a file called book.js and paste the following code:
在/app/routes/創建一個名為book.js的文件,并粘貼以下代碼:
let mongoose = require('mongoose'); let Book = require('../models/book');/** GET /book route to retrieve all the books.*/ function getBooks(req, res) {//Query the DB and if no errors, send all the bookslet query = Book.find({});query.exec((err, books) => {if(err) res.send(err);//If no errors, send them back to the clientres.json(books);}); }/** POST /book to save a new book.*/ function postBook(req, res) {//Creates a new bookvar newBook = new Book(req.body);//Save it into the DB.newBook.save((err,book) => {if(err) {res.send(err);}else { //If no errors, send it back to the clientres.json({message: "Book successfully added!", book });}}); }/** GET /book/:id route to retrieve a book given its id.*/ function getBook(req, res) {Book.findById(req.params.id, (err, book) => {if(err) res.send(err);//If no errors, send it back to the clientres.json(book);}); }/** DELETE /book/:id to delete a book given its id.*/ function deleteBook(req, res) {Book.remove({_id : req.params.id}, (err, result) => {res.json({ message: "Book successfully deleted!", result });}); }/** PUT /book/:id to updatea a book given its id*/ function updateBook(req, res) {Book.findById({_id: req.params.id}, (err, book) => {if(err) res.send(err);Object.assign(book, req.body).save((err, book) => {if(err) res.send(err);res.json({ message: 'Book updated!', book });}); }); }//export all the functions module.exports = { getBooks, postBook, getBook, deleteBook, updateBook };Here the key concepts:
這里的關鍵概念:
- The routes are no more than standard routes, GET, POST, DELETE, PUT to perform CRUD operations on our data. 這些路由只不過是用于對數據執行CRUD操作的標準路由,GET,POST,DELETE,PUT。
- In the function updatedBook() we use Object.assign, a new function introduced in ES6 which, in this case, overrides the common properties of book with req.body while leaving untouched the others. 在功能updatedBook()我們使用Object.assign ,在ES6推出了新的功能,在這種情況下,將覆蓋本書的公共屬性與req.body同時保持不變的人。
- At the end we export the object using a faster syntax which pairs key and value to avoid useless repetitions. 最后,我們使用更快的語法導出對象,該語法將鍵和值配對,以避免無用的重復。
We finished this section and actually we have a working app!
我們完成了本節,實際上我們有一個可以運行的應用程序!
天真測試 ( A Naive Test )
Now let's run the app and open POSTMAN to send HTTP request to the server and check if everything is working as expected.
現在,讓我們運行該應用程序并打開POSTMAN以將HTTP請求發送到服務器,并檢查一切是否按預期工作。
in the command line run
在命令行中運行
npm start獲取/預訂 (GET /book)
in POSTMAN run the GET request and, assuming the database contains books, here is the result:
在POSTMAN中運行GET請求,并假設數據庫包含書籍,則結果如下:
The server correctly returned the book list in my database.
服務器正確返回了我數據庫中的書單。
開機自檢/預訂 (POST /book)
Let's add a book and POST to the server:
讓我們向服務器添加一本書和POST:
It seems the book was perfectly added. The server returned the book and a message confirming it was added in our bookstore. Is it true? Let's send another GET request and here is the result:
看來這本書已完美地添加了。 服務器返回了該書,并顯示一條消息,確認已將其添加到我們的書店中。 是真的嗎 讓我們發送另一個GET請求,結果如下:
Awesome it works!
太棒了!
放置/ book /:id (PUT /book/:id)
Let's update a book by changing the page and check the result:
讓我們通過更改頁面來更新一本書并檢查結果:
Great! PUT also seems to be working so let's send another GET request to check all the list:
大! PUT似乎也可以正常工作,因此讓我們發送另一個GET請求來檢查所有列表:
All is running smoothly...
一切運行順利...
GET / book /:id (GET /book/:id)
Now let's get a single book by sending the id in the GET request and then delete it:
現在,通過發送GET請求中的ID,然后將其刪除來獲得一本書:
As it returns the correct book let's try now to delete it:
當它返回正確的書時,讓我們現在嘗試刪除它:
刪除/ book /:id (DELETE /book/:id )
Here is the result of the DELETE request to the server:
這是對服務器的DELETE請求的結果:
Even the last request works smoothly and we do not need to doublecheck with another GET request as we are sending the client some info from mongo (result property) which states the book was actually deleted.
即使最后一個請求也可以正常工作,并且我們不需要從另一個GET請求中仔細檢查,因為我們正在從mongo(結果屬性)向客戶端發送一些信息,該信息指出該書實際上已被刪除。
By doing some test with POSTMAN the app happened to behave as expected right? So, would you shoot it to your clients?
通過對POSTMAN進行測試,該應用程序的行為恰好符合預期,對嗎? 那么,您會把它拍給客戶嗎?
Let me reply for you: NO!!
讓我為您回復: 不!
Ours is what I called a naive test because we simply tried few operations without testing strange situations that may happen: A post request without some expected data, a DELETE with a wrong id as parameter or even without id to name few.
我們之所以稱其為“天真測試”,是因為我們只是嘗試了很少的操作而沒有測試可能發生的奇怪情況:沒有某些預期數據的發布請求,ID作為參數錯誤的DELETE或什至沒有ID的例子。
This is obviously a simple app and if we were lucky enough, we coded it without introducing bugs of any sort, but what about a real-world app? Moreover, we spent time to run with POSTMAN some test HTTP requests so what would happen if one day we had to change the code of one of those? Test them all again with POSTMAN? Have you started to realize this is not an agile approach?
顯然,這是一個簡單的應用程序,如果幸運的話,我們在編寫代碼時不會引入任何類型的錯誤,但是真實世界中的應用程序呢? 此外,我們花了一些時間與POSTMAN一起運行一些測試HTTP請求,因此如果有一天我們不得不更改其中一個的代碼,會發生什么? 用POSTMAN再次測試它們嗎? 您是否已經開始意識到這不是敏捷方法?
This is nothing but few situations you may encounter and you already encountered in your journey as a developer, luckily we have tools to create tests which are always available and can be launched with a single comman line.
這不過是您可能遇到的少數情況,并且您在開發人員的旅途中已經遇到過這種情況,幸運的是,我們擁有創建測試的工具,這些工具始終可用,并且可以通過一條命令行啟動。
Let's do something better to test our app!
讓我們做點更好的測試我們的應用程序吧!
更好的測試 ( A Better Test )
First, let's create a file in /test called book.js and paste the following code:
首先,讓我們在/test創建一個名為book.js的文件,并粘貼以下代碼:
//During the test the env variable is set to test process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');//Require the dev-dependencies let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp); //Our parent block describe('Books', () => {beforeEach((done) => { //Before each test we empty the databaseBook.remove({}, (err) => { done(); }); }); /** Test the /GET route*/describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});});Wow that's a lot of new things, let's dig into it:
哇,這是很多新東西,讓我們對其進行深入研究:
So it starts with "describe" blocks of code for better organizing your assertions and this organization will reflect in the output at command line as we will see later.
因此,它從“描述”代碼塊開始,以更好地組織您的斷言,并且該組織將反映在命令行的輸出中,我們將在后面看到。
beforeEach is a block of code that is going to run before each the describe blocks on the same level. Why we did that? Well we are going to remove any book from the database to start with an empty bookstore whenever a test is run.
beforeEach是一個代碼塊,它將在同一級別的每個describe塊之前運行。 我們為什么這樣做? 好了,我們將在運行測試時從數據庫中刪除所有書籍,以一個空的書店開始。
測試/ GET路線 (Test the /GET route)
And here it comes the first test, chai is going to perform a GET request to the server and the assertions on the res variable will satisfy or reject the first parameter of the the it block it should GET all the books. Precisely, given the empty bookstore the result of the request should be:
這是第一個測試,chai將向服務器執行GET請求,并且res變量上的斷言將滿足或拒絕it塊的第一個參數,即它應該獲取所有書籍 。 精確地,給定空的書店,請求的結果應為:
Notice that the syntax of should assertions is very intituitive as it is similar as a natural language statement.
注意, should斷言的語法非常直觀,因為它類似于自然語言語句。
Now, in the command line run:
```javascript npm test ```現在,在命令行中運行:
JavaScript npm測試and here it is the output:
這是輸出:
The test passed and the output reflects the way we organized our code with blocks of describe.
測試通過,輸出結果反映了我們使用describe塊組織代碼的方式。
測試/ POST路由 (Test the /POST route)
Now let's check our robust is our API, suppose we are trying to add a book with missing pages field passed to the server: The server should not respond with a proper error message.
現在,讓我們檢查一下我們健壯的API,假設我們正在嘗試添加一本書,并將其缺少的頁面字段傳遞給服務器:服務器不應以正確的錯誤消息進行響應。
Copy and paste the following code in the test file:
將以下代碼復制并粘貼到測試文件中:
process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp);describe('Books', () => {beforeEach((done) => {Book.remove({}, (err) => { done(); }); });describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});/** Test the /POST route*/describe('/POST book', () => {it('it should not POST a book without pages field', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('errors');res.body.errors.should.have.property('pages');res.body.errors.pages.should.have.property('kind').eql('required');done();});});}); });Here we added the test on an incomplete /POST request, let's analyze the assertions:
在這里,我們在不完整的/ POST請求上添加了測試,讓我們分析斷言:
NB notice that we send the book along with the POST request by the .send() function.
請注意,我們通過.send()函數將書籍與POST請求一起發送。
Let's run the same command again and here is the output:
讓我們再次運行相同的命令,這是輸出:
Oh Yeah our test test is correct!
哦,是的,我們的測試測試是正確的!
Before writing a new test let me precise two things:
在編寫新測試之前,讓我先詳細說明兩點:
Let's send a book with all the required fields this time. Copy and paste the following code in the test file:
這次發送一本包含所有必填字段的書。 將以下代碼復制并粘貼到測試文件中:
process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp);describe('Books', () => {beforeEach((done) => {Book.remove({}, (err) => { done(); }); });describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});/** Test the /POST route*/describe('/POST book', () => {it('it should not POST a book without pages field', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('errors');res.body.errors.should.have.property('pages');res.body.errors.pages.should.have.property('kind').eql('required');done();});});it('it should POST a book ', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954,pages: 1170}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book successfully added!');res.body.book.should.have.property('title');res.body.book.should.have.property('author');res.body.book.should.have.property('pages');res.body.book.should.have.property('year');done();});});}); });This time we expect a returning object with a message saying we succesfully added the book and the book itself (remember with POSTMAN?). You should be now quite familiar with the assertions I made so there is no need for going into detail. Instead, run the command again and here is the output:
這次我們希望返回的對象帶有一條消息,說我們成功添加了這本書和這本書本身(還記得POSTMAN嗎?)。 您現在應該非常熟悉我所做的斷言,因此無需贅述。 而是再次運行命令,這是輸出:
Smooth~
順利?
測試/ GET /:id路線 (Test /GET/:id Route)
Now let's create a book, save it into the database and use the id to send a GET request to the server. Copy and paste the following code in the test file:
現在,讓我們創建一本書,將其保存到數據庫中,并使用ID將GET請求發送到服務器。 將以下代碼復制并粘貼到測試文件中:
process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp);describe('Books', () => {beforeEach((done) => {Book.remove({}, (err) => { done(); }); });describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});describe('/POST book', () => {it('it should not POST a book without pages field', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('errors');res.body.errors.should.have.property('pages');res.body.errors.pages.should.have.property('kind').eql('required');done();});});it('it should POST a book ', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954,pages: 1170}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book successfully added!');res.body.book.should.have.property('title');res.body.book.should.have.property('author');res.body.book.should.have.property('pages');res.body.book.should.have.property('year');done();});});});/** Test the /GET/:id route*/describe('/GET/:id book', () => {it('it should GET a book by the given id', (done) => {let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });book.save((err, book) => {chai.request(server).get('/book/' + book.id).send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('title');res.body.should.have.property('author');res.body.should.have.property('pages');res.body.should.have.property('year');res.body.should.have.property('_id').eql(book.id);done();});});});}); });Through the assertions we made sure the server returned all the fields and the right book testing the two ids together. Here is the output:
通過這些斷言,我們確保服務器返回了所有字段,并且正確地測試了兩個ID 。 這是輸出:
Have you noticed that by testing single routes within independent blocks we provide a very clear output? Also, isn't it so efficient? We wrote several tests that can be repeated with a single command line, once and for all.
您是否注意到通過測試獨立塊內的單個路徑,我們提供了非常清晰的輸出? 而且,這樣有效嗎? 我們編寫了幾個測試,這些測試可以用一個命令行一次又一次地重復進行。
測試/ PUT /:id路由 (Test the /PUT/:id Route)
Time for testing an update on one of our books, we first save the book and then update the year it was published. So, copy and paste the following code:
是時候測試我們其中一本書的更新了,我們首先保存這本書,然后更新它的出版年份。 因此,復制并粘貼以下代碼:
process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp);describe('Books', () => {beforeEach((done) => {Book.remove({}, (err) => { done(); }); });describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});describe('/POST book', () => {it('it should not POST a book without pages field', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('errors');res.body.errors.should.have.property('pages');res.body.errors.pages.should.have.property('kind').eql('required');done();});});it('it should POST a book ', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954,pages: 1170}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book successfully added!');res.body.book.should.have.property('title');res.body.book.should.have.property('author');res.body.book.should.have.property('pages');res.body.book.should.have.property('year');done();});});});describe('/GET/:id book', () => {it('it should GET a book by the given id', (done) => {let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });book.save((err, book) => {chai.request(server).get('/book/' + book.id).send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('title');res.body.should.have.property('author');res.body.should.have.property('pages');res.body.should.have.property('year');res.body.should.have.property('_id').eql(book.id);done();});});});});/** Test the /PUT/:id route*/describe('/PUT/:id book', () => {it('it should UPDATE a book given the id', (done) => {let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})book.save((err, book) => {chai.request(server).put('/book/' + book.id).send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778}).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book updated!');res.body.book.should.have.property('year').eql(1950);done();});});});}); });We wanna make sure the message is the correct Book updated! one and that the year field was actually updated. Here is the output:
我們要確保郵件是正確的更新書! 一, year字段實際上已更新。 這是輸出:
Good, we are close to the end, we still gotta test the DELETE route.
很好,我們快結束了,我們仍然要測試DELETE路線。
測試/ DELETE /:id路由 (Test the /DELETE/:id Route)
The pattern is similar to the previous tests, we first store a book, delete it and test against the response. Copy and paste the following code:
該模式類似于先前的測試,我們首先存儲一本書,將其刪除并根據響應進行測試。 復制并粘貼以下代碼:
process.env.NODE_ENV = 'test';let mongoose = require("mongoose"); let Book = require('../app/models/book');let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../server'); let should = chai.should();chai.use(chaiHttp);describe('Books', () => {beforeEach((done) => {Book.remove({}, (err) => { done(); }); });describe('/GET book', () => {it('it should GET all the books', (done) => {chai.request(server).get('/book').end((err, res) => {res.should.have.status(200);res.body.should.be.a('array');res.body.length.should.be.eql(0);done();});});});describe('/POST book', () => {it('it should not POST a book without pages field', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('errors');res.body.errors.should.have.property('pages');res.body.errors.pages.should.have.property('kind').eql('required');done();});});it('it should POST a book ', (done) => {let book = {title: "The Lord of the Rings",author: "J.R.R. Tolkien",year: 1954,pages: 1170}chai.request(server).post('/book').send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book successfully added!');res.body.book.should.have.property('title');res.body.book.should.have.property('author');res.body.book.should.have.property('pages');res.body.book.should.have.property('year');done();});});});describe('/GET/:id book', () => {it('it should GET a book by the given id', (done) => {let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });book.save((err, book) => {chai.request(server).get('/book/' + book.id).send(book).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('title');res.body.should.have.property('author');res.body.should.have.property('pages');res.body.should.have.property('year');res.body.should.have.property('_id').eql(book.id);done();});});});});describe('/PUT/:id book', () => {it('it should UPDATE a book given the id', (done) => {let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})book.save((err, book) => {chai.request(server).put('/book/' + book.id).send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778}).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book updated!');res.body.book.should.have.property('year').eql(1950);done();});});});});/** Test the /DELETE/:id route*/describe('/DELETE/:id book', () => {it('it should DELETE a book given the id', (done) => {let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})book.save((err, book) => {chai.request(server).delete('/book/' + book.id).end((err, res) => {res.should.have.status(200);res.body.should.be.a('object');res.body.should.have.property('message').eql('Book successfully deleted!');res.body.result.should.have.property('ok').eql(1);res.body.result.should.have.property('n').eql(1);done();});});});}); });Again the server returns a message and properties from mongoose that we assert so let's check the output:
再次,服務器從貓鼬返回一條消息和我們聲明的屬性,因此讓我們檢查輸出:
Great, our tests are all positive and we have a good basis to continue testing our routes with more sophisticated assertions.
太好了,我們的測試都是積極的,并且我們有很好的基礎繼續使用更復雜的斷言來測試我們的路線。
Congratulation for completing the tutorial!
祝賀您完成本教程!
結論 ( Conclusion )
In this tutorial we faced the problem of testing our routes to provide our users a stable experience.
在本教程中,我們面臨著測試路線以為用戶提供穩定體驗的問題。
We went through all the steps of creating a RESTful API, doing a naive test with POSTMAN and then propose a better way to test, in fact the main topic of the tutorial.
我們經歷了創建RESTful API的所有步驟,使用POSTMAN進行了幼稚的測試,然后提出了一種更好的測試方法,實際上是本教程的主題。
It is good habit to always spend some time making tests to assure a server as reliable as possible but unfortunately it is often underestimated.
習慣上總是花一些時間進行測試以確保服務器盡可能可靠,但這是一個好習慣,但是不幸的是,它經常被低估。
During the tutorial we also discuss a few benefits of code testing and this will open doors to more advanced topics such as Test Driven Development (TDD).
在本教程中,我們還將討論代碼測試的一些好處,這將為諸如測試驅動開發(TDD)之類的更高級主題打開大門。
Good job!
做得好!
額外的ock子 ( Bonus Mockgoose )
One may argue that using two different databases is not the best situation and a second one is often not available. So what to do? Well, there is an alternative: Mockgoose.
一個人可能會爭辯說使用兩個不同的數據庫并不是最好的情況,而第二個數據庫通常不可用。 那么該怎么辦? 好吧,還有另一種選擇:Mockgoose。
Basically Mockgoose wraps your mongoose driver by intercepting the connection so your database will not be touched but instead use in memory store. Moreover it integrates well with Mocha.
基本上, Mockgoose通過攔截連接來包裝您的Mongoose驅動程序,這樣您的數據庫就不會被觸摸,而是在內存存儲中使用。 此外,它與Mocha集成得很好。
NB: Apparently it requires mongodb to be installed on the running machine.
注意 :顯然,它要求在運行的計算機上安裝mongodb。
翻譯自: https://scotch.io/tutorials/test-a-node-restful-api-with-mocha-and-chai
node mocha
總結
以上是生活随笔為你收集整理的node mocha_使用Mocha和Chai测试Node RESTful API的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: R048---UiPath中四种筛选数据
- 下一篇: VINS_FUSION