序章

大きなアプリのバックエンド部分をノードで最終的に作成できたことの満足感を今でも覚えています。多くの人もそうしていると思います。

その後? アプリが期待どおりに動作することを確認する必要があります。強く推奨される方法の1つは、ソフトウェアテストです。 ソフトウェアテストは、新しい機能がシステムに追加されるたびに非常に役立ちます。単一のコマンドで実行できるテスト環境がすでにセットアップされていると、新しい機能が新しいバグをもたらすかどうかを判断するのに役立ちます。

これまで、JSONWebトークンとパスポートを使用したノードAPI認証に取り組んできました。

このチュートリアルでは、Node.jsを使用して単純なRESTful APIを作成し、MochaChaiを使用してそれに対するテストを作成します。 書店でCRUDの動作をテストします。

いつものように、チュートリアル全体を通してアプリを段階的に構築することも、githubで直接入手することもできます。

Mocha:テスト環境

Mocha は、非同期テストを可能にするNode.js用のjavascriptフレームワークです。 お気に入りのアサーションライブラリを使用してコードをテストできる環境を提供するとします。

mocha-homepage

モカにはたくさんの素晴らしい機能があり、ウェブサイトには長いリストが表示されていますが、私が最も気に入っているものは次のとおりです。

  • promiseを含む単純な非同期サポート。
  • 非同期テストタイムアウトのサポート。
  • 前、後、それぞれの前、各フックの後(各テストを行う環境をクリーンアップするのに非常に便利です!)。
  • チュートリアルでは、必要なアサーションライブラリのChaiを使用してください。

チャイ:アサーションライブラリ

したがって、Mochaを使用すると、実際にテストを行うための環境がありますが、たとえばHTTP呼び出しをテストするにはどうすればよいでしょうか。 さらに、定義された入力が与えられた場合、GETリクエストが実際に期待するJSONファイルを返しているかどうかをテストするにはどうすればよいですか? アサーションライブラリが必要です。そのため、mochaでは不十分です。

これがChaiで、現在のチュートリアルのアサーションライブラリです。

chai homepage

Chaiは、私たちが好むインターフェースを自由に選択できることに光を当てています。「すべき」、「期待する」、「主張する」すべてが利用可能です。 私は個人的にshouldを使用しますが、 API をチェックして、他の2つに自由に切り替えることができます。 最後に、 Chai HTTP アドオンを使用すると、Chaiライブラリは必要に応じてHTTPリクエストでアサーションを簡単に使用できます。

前提条件

  • Node.js :node.jsの基本的な理解であり、RESTfulAPIの構築についてはあまり詳しく説明しないのでお勧めします。
  • APIへの高速HTTPリクエストを行うためのPOSTMAN。
  • ES6構文:コードの可読性を高めるためにES6機能が最も統合された最新バージョンのノード(6。*。*)を使用することにしました。 ES6に慣れていない場合は、すばらしいスコッチ記事( Pt.1 Pt.2 Pt.3 )をご覧ください。しかし、「エキゾチックな」構文や宣言に出くわすたびに、私がいくつかの単語を費やすのではないかと心配しないでください。

書店を立ち上げる時が来ました!

プロジェクトの設定

ディレクトリ構造

これが私たちのAPIのプロジェクトディレクトリです。これは前に見たことがあるはずです。

-- controllers 
---- models
------ book.js
---- routes
------ book.js
-- config
---- default.json
---- dev.json
---- test.json
-- test
---- book.js
package.json
server.json

3つのJSONファイルを含む/configフォルダーに注意してください。名前が示すように、特定の目的のための特定の構成が含まれています。

このチュートリアルでは、開発用とテスト用の2つのデータベースを切り替えます。したがって、ファイルにはJSON形式のmongodbURIが含まれています。

dev.json AND default.json
{ "DBHost": "YOUR_DB_URI" }
test.json
{ "DBHost": "YOUR_TEST_DB_URI" }

NB default.jsonはオプションですが、configディレクトリ内のファイルがそこからロードされることを強調しておきます。 構成ファイル(構成ディレクトリ、ファイルの順序、ファイル形式など)の詳細については、このリンクを確認してください。

最後に、/test/book.jsに注目してください。ここで、テストを作成します。

Package.json

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"
  }
}

繰り返しになりますが、構成はnode.jsを使用してサーバー以上を作成した人を驚かせるべきではありません。テスト関連のパッケージ mocha chai chai-httpが保存されますdev-dependencies(コマンドラインからのフラグ--save-dev)で、scriptsプロパティはサーバーを実行する2つの異なる方法を可能にします。

mochaを実行するために、フラグ--timeout 10000を追加しました。これは、mongolabでホストされているデータベースからデータをフェッチするため、デフォルトの2秒では不十分な場合があるためです。

おめでとうございます! チュートリアルの退屈な部分を終えたので、今度はサーバーを作成してテストします。

サーバー

主要

プロジェクトのルートにファイル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 line
    app.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 testing

重要な概念は次のとおりです。

  • モジュールconfigは、 NODE_ENV コンテンツという名前の構成ファイルにアクセスして、db接続のmongodbURIパラメーターを取得する必要があります。 これは、アプリの将来のユーザーに隠されている別のデータベースでテストすることにより、「実際の」データベースをクリーンに保つのに役立ちます。
  • 環境変数NODE_ENVは、 test に対してテストされ、コマンドラインで morgan ログを無効にします。そうしないと、テスト出力に干渉します。
  • コードの最後の行は、テスト目的でサーバーをエクスポートします。
  • letを使用した変数定義に注意してください。これにより、変数は最も近い囲みブロックに囲まれ、ブロックの外側にある場合はグローバルになります。

コードの残りの行は新しいものではありません。必要なすべてのモジュールを要求し、サーバーとの通信用のヘッダーオプションを定義し、特定のルートを作成し、最終的にサーバーが定義されたポートでリッスンできるようにします。

モデルとルート

私たちの本のモデルの時間です! /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);

私たちの本のスキーマには、タイトル、著者、ページ数、発行年、データベースの作成日があります。 チュートリアルの目的には役に立たないため、versionKeyをfalseに設定しました。

NB :のエキゾチックなコールバック構文.pre()関数は矢印関数であり、構文が短い関数であり、 MDN 、「this値を字句的にバインドします(独自のthis、arguments、super、またはnew.targetをバインドしません)。 矢印関数は常に匿名です」

さて、モデルについて知る必要があるほとんどすべてなので、ルートに移りましょう。

/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 books
    let query = Book.find({});
    query.exec((err, books) => {
        if(err) res.send(err);
        //If no errors, send them back to the client
        res.json(books);
    });
}

/*
 * POST /book to save a new book.
 */
function postBook(req, res) {
    //Creates a new book
    var 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 client
            res.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 client
        res.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 };

ここで重要な概念:

  • ルートは、データに対してCRUD操作を実行するための標準ルートであるGET、POST、DELETE、PUTにすぎません。
  • 関数updatedBook()では、Object.assignを使用します。これは、ES6で導入された新しい関数で、この場合、本の一般的なプロパティをreq.bodyでオーバーライドし、他のプロパティはそのままにします。
  • 最後に、キーと値を組み合わせて無駄な繰り返しを避ける、より高速な構文を使用してオブジェクトをエクスポートします。

このセクションを終了し、実際に動作するアプリがあります。

ナイーブテスト

次に、アプリを実行し、POSTMANを開いてHTTPリクエストをサーバーに送信し、すべてが期待どおりに機能しているかどうかを確認します。

コマンドラインで実行

npm start

GET / book

POSTMANでGETリクエストを実行し、データベースに本が含まれていると仮定すると、結果は次のようになります。

サーバーは私のデータベースの本のリストを正しく返しました。

POST / book

本を追加してサーバーにPOSTしてみましょう。

その本は完全に追加されたようです。 サーバーが本を返し、それが書店に追加されたことを確認するメッセージが表示されました。 それは本当ですか? 別のGETリクエストを送信してみましょう。結果は次のとおりです。

素晴らしいそれは動作します!

PUT / book /:id

ページを変更して本を更新し、結果を確認してみましょう。

素晴らしい! PUTも機能しているようですので、別のGETリクエストを送信して、すべてのリストを確認しましょう。

すべてが順調に進んでいます…

GET / book /:id

次に、GETリクエストでIDを送信して1冊の本を取得し、それを削除しましょう。

正しい本が返されるので、今すぐ削除してみましょう。

/ book /:idを削除

サーバーへのDELETEリクエストの結果は次のとおりです。

最後のリクエストでもスムーズに機能し、本が実際に削除されたことを示す情報をmongo(結果プロパティ)からクライアントに送信しているため、別のGETリクエストで再確認する必要はありません。

POSTMANでテストを行うことで、アプリはたまたま期待どおりに動作しましたか? それで、あなたはそれをあなたのクライアントに撃ちますか?

返信させてください: NO !!

発生する可能性のある奇妙な状況をテストせずに、いくつかの操作を試しただけなので、私たちのテストはナイーブテストと呼んでいます。

これは明らかに単純なアプリであり、運が良ければ、どんな種類のバグも導入せずにコーディングしましたが、実際のアプリはどうでしょうか。 さらに、POSTMANでいくつかのテストHTTPリクエストを実行するために時間を費やしたので、ある日、それらの1つのコードを変更しなければならなかった場合はどうなるでしょうか。 POSTMANでそれらすべてをもう一度テストしますか? これはアジャイルアプローチではないことに気づき始めましたか?

これはあなたが遭遇する可能性のあるいくつかの状況に過ぎず、開発者としての旅の中ですでに遭遇しました。幸いなことに、常に利用可能で、1行のカンマ行で起動できるテストを作成するツールがあります。

私たちのアプリをテストするためにもっと良いことをしましょう!

より良いテスト

まず、/testbook.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 database
        Book.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();
            });
      });
  });

});

うわー、それはたくさんの新しいことです、それを掘り下げましょう:

  1. NODE_ENV 変数をテストに設定していることに気付いたはずです。そうすることで、サーバーがテストデータベースに接続し、cmdのモーガンログを回避するように、ロードする構成ファイルを変更します。

  2. dev-dependenciesモジュールとサーバー自体が必要でした(module.exportsでエクスポートしたことを覚えていますか?)。

  3. shouldを定義するには、chai.should()を実行して、HTTPリクエストの結果に対するテストのスタイルを設定してから、chaiにchaiHTTPを使用するように指示しました。

したがって、アサーションをより適切に編成するためのコードの「記述」ブロックから始まり、この編成は、後で説明するように、コマンドラインでの出力に反映されます。

beforeEachは、同じレベルの各describeブロックの前に実行されるコードのブロックです。 なぜそれをしたのですか? テストを実行するたびに、データベースから本を削除して、空の書店から始めます。

/GETルートをテストします

そしてここで最初のテストが行われます。chaiはサーバーに対してGETリクエストを実行し、res変数のアサーションはitブロックの最初のパラメーターを満たすか拒否しますすべてをGETする必要があります本。 正確には、空の書店が与えられた場合、リクエストの結果は次のようになります。

  1. ステータス200。
  2. 結果は配列になります。
  3. 書店は空なので、長さは0と推定しました。

should アサーションの構文は、自然言語のステートメントに似ているため、非常に直感的であることに注意してください。

次に、コマンドラインで次のコマンドを実行します。

“ `javascript npm test “ `

そしてここにそれは出力です:

テストに合格し、出力はdescribeのブロックを使用してコードを編成した方法を反映しています。

/POSTルートをテストします

ここで、堅牢なAPIがAPIであることを確認しましょう。サーバーに渡されたページフィールドが欠落している本を追加しようとしていると仮定します。サーバーは適切なエラーメッセージで応答しないはずです。

次のコードをコピーしてテストファイルに貼り付けます。

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();
            });
      });

  });
});

ここで、不完全な/ POSTリクエストにテストを追加しました。アサーションを分析してみましょう:

  1. ステータスは200である必要があります。
  2. 応答本文はオブジェクトである必要があります。
  3. ボディプロパティの1つはerrorsである必要があります。
  4. Errorsには、プロパティとして欠落しているフィールドページが必要です。
  5. 最後に、pagesは、サーバーから否定的な回答を得た理由を強調するために、requiredと等しいプロパティkindを持つ必要があります。

NBは、.send()関数によるPOSTリクエストと一緒に本を送信することに注意してください。

同じコマンドをもう一度実行してみましょう。出力は次のとおりです。

そうそう、私たちのテストテストは正しいです!

新しいテストを書く前に、2つのことを正確に説明しましょう。

  1. まず第一に、なぜサーバーの応答がそのように構成されているのですか? / POSTルートのコールバック関数を読み取ると、必須フィールドが欠落している場合、サーバーがマングースからエラーメッセージを送り返すことに気付くでしょう。 POSTMANを試して、応答を確認してください。
  2. フィールドが欠落している場合でも、ステータスは200を返します。これは、ルートのテストを学習しているだけなので、簡単にするためです。 ただし、代わりに206部分コンテンツのステータスを返すことをお勧めします

今回は必須フィールドがすべて揃った本を送りましょう。 次のコードをコピーしてテストファイルに貼り付けます。

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();
            });
      });
  });
});

今回は、本と本自体を正常に追加したことを示すメッセージを含むオブジェクトが返されることを期待しています(POSTMANを覚えていますか?)。 これで、私が行ったアサーションに精通しているはずなので、詳細に立ち入る必要はありません。 代わりに、コマンドを再度実行すると、出力は次のようになります。

スムーズ〜

/ GET /:idルートをテストします

それでは、本を作成してデータベースに保存し、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();
            });
          });

      });
  });
});

アサーションを通じて、サーバーがすべてのフィールドと、2つのidsを一緒にテストする正しい本を返すことを確認しました。 出力は次のとおりです。

独立したブロック内の単一ルートをテストすることにより、非常に明確な出力が提供されることに気づきましたか? また、それはとても効率的ではありませんか? 1つのコマンドラインで一度だけ繰り返すことができるいくつかのテストを作成しました。

/ PUT /:idルートをテストします

いずれかの書籍の更新をテストするときは、最初に書籍を保存してから、発行された年を更新します。 したがって、次のコードをコピーして貼り付けます。

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();
                });
          });
      });
  });
});

メッセージが正しいブックが更新されたことを確認したい! 1つであり、yearフィールドが実際に更新されたことを確認します。 出力は次のとおりです。

いいですね、終わりに近づいています。まだDELETEルートをテストする必要があります。

/ DELETE /:idルートをテストします

パターンは前のテストと似ています。最初に本を保存し、それを削除して、応答に対してテストします。 次のコードをコピーして貼り付けます。

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();
                });
          });
      });
  });
});

ここでも、サーバーはマングースからメッセージとプロパティを返すので、出力を確認しましょう。

すばらしいです。私たちのテストはすべて肯定的であり、より洗練されたアサーションを使用してルートをテストし続けるための良い基盤があります。

チュートリアルを完了しておめでとうございます!

結論

このチュートリアルでは、ユーザーに安定したエクスペリエンスを提供するためにルートをテストするという問題に直面しました。

RESTful APIを作成し、POSTMANを使用して単純なテストを実行し、テストのより良い方法を提案するすべてのステップを実行しました。実際、チュートリアルのメイントピックです。

サーバーの信頼性を可能な限り確保するために、常にテストを行うのは良い習慣ですが、残念ながら、過小評価されることがよくあります。

チュートリアルでは、コードテストのいくつかの利点についても説明します。これにより、テスト駆動開発(TDD)などのより高度なトピックへの扉が開かれます。