Docs Menu
Docs Home
/ /
Atlas App Services
/

Atlas Function のテスト

項目一覧

  • 始める前に
  • 関数のユニットテスト
  • App Services Appのローカル コピーを取得する
  • 新規関数の作成
  • 関数コードの書込み
  • ユニット テストで使用するためのエクスポート関数
  • エクスポートされた 関数 コードのユニット テスト
  • モック サービス
  • 関数の統合テスト
  • テスト アプリを作成する
  • ライブ環境でテストする

このページでは、Atlas Function をテストするために使用できるいくつかの方法について説明します。

関数 JavaScript ランタイムと標準の Node.js ランタイムは異なるため、関数をテストする際にはいくつか独自の考慮事項を考慮する必要があります。 このページでは、関数の一意性を処理する方法について説明します。

Atlas Function をテストするには、次のものが必要です。

  • Atlas App Services アプリ。 作成方法については、「アプリの作成 」を参照してください。

  • アプリを構成するための コード配置 メソッド。 次のいずれかを選択します。

    • ローカル システム PATHにインストールおよび追加された App Services CLI のコピー。 詳しくは、「 App Services CLI のインストール 」を参照してください。

    • アプリの 構成ファイル を保持して配置するように構成されたGithubリポジトリ 。 その設定方法については、「 Githubを使用した自動配置 」を参照してください。

ユニット テストを使用して、関数の機能を検証できます。 Node.js 互換の 関数をテストするためのテストフレームワーク。 このページの例では、 Jest テスト フレームワークを使用します。

CommonJS モジュール を使用する必要があります : 関数のユニット テストを作成します。

1

サーバーからアプリの最新構成を取得します。

appservices pull --remote <App ID>

Github からアプリの最新構成を取得します。

git pull <Remote Name> <Branch name>
2

新しい関数を作成する。 アプリの構成ファイルで、関数のfunctionsディレクトリに新しい JavaScript ファイルを作成します。

touch functions/hello.js

また、関数の構成情報をfunctions/config.jsonに追加する必要があります。

{
"name": "hello",
"private": false,
"run_as_system": true
},

Tip

以下も参照してください。

新しい関数の作成の詳細については、「 関数の定義 」を参照してください。

3

関数コードを簡単にテストできるようにするには、個別のコンポーネントに分割してモジュール型にします。 関数のすべてのロジックは、前の手順で定義したファイルに保持する必要があります。 関数 ファイルでは、プロジェクト内の他のファイルからの相対インポートは実行できません。 npm を使用して依存関係をインポートすることもできます。

関数をエクスポートするには、 exportsに割り当てて関数をエクスポートする必要があります。

hello.js
function greet(word) {
return "hello " + word;
}
function greetWithPunctuation(word, punctuation) {
return greet(word) + punctuation;
}
// Function exported to App Services
exports = greetWithPunctuation;
4

Node.js ユニットのテスト ファイルで使用するコードをエクスポートするには、CommonJS module.exports構文を使用する必要があります。

この構文は 関数のランタイムと互換性がありません。 Atlas Functions 環境では、Node.js グローバルmoduleは提供されません。 関数と互換性のあるファイルを維持しながらユニット テストにモジュールをエクスポートするには、 module.exportsステートメントを チェックでラップし、グローバルmoduleオブジェクトが存在するかどうかを確認します。

functions/hello.js
function greet(word) {
return "hello " + word;
}
function greetWithPunctuation(word, punctuation) {
return greet(word) + punctuation;
}
// Function exported to App Services
exports = greetWithPunctuation;
// export locally for use in unit test
if (typeof module !== "undefined") {
module.exports = { greet, greetWithPunctuation };
}
5

これで、 関数 ファイルからエクスポートしたモジュールのユニット テストを記述できます。 プロジェクト内の別のtestディレクトリに関数ファイルのテスト ファイルを作成します。

mkdir -p test/unit
touch test/unit/hello.test.js

前のステップでエクスポートしたモジュールをインポートし、ユニット テストを追加します。

test/unit/hello.test.js
const { greet, greetWithPunctuation } = require("../../functions/hello");
test("should greet", () => {
const helloWorld = greet("world");
expect(helloWorld).toBe("hello world");
});
test("should greet with punctuation", () => {
const excitedHelloWorld = greetWithPunctuation("world", "!!!");
expect(excitedHelloWorld).toBe("hello world!!!");
});

グローバル コンテキスト オブジェクトまたは関数が公開するその他のグローバル モジュールのいずれかを使用する関数のユニット テストを作成するには、その動作のモックを作成する必要があります。

この例では、関数はcontext.values.get()経由でApp Services Valueを参照し、グローバル モジュールBSON を使用して ObjectId を作成します。

accessAppServicesGlobals.js
function accessAppServicesGlobals() {
const mongodb = context.services.get("mongodb-atlas");
const objectId = BSON.ObjectId()
// ... do stuff with these values
}
exports = accessAppServicesGlobals;
if (typeof module !== "undefined") {
module.exports = accessAppServicesGlobals;
}

これらのモックを Node.js グローバル名前空間にアタッチします。 これにより、関数ランタイムでの方法と同じ方法でユニット テストでモックを呼び出すことができます。

global.context = {
// whichever global context methods you want to mock.
// 'services', 'functions', values, etc.
}
// you can also mock other Functions global modules
global.BSON = {
// mock methods
}

また、グローバル名前空間を汚染しないように、セットアップ ブロックと引き継ぎブロックでこれらのモックを宣言して削除することもできます。

// adds context mock to global namespace before each test
beforeEach(() => {
global.context = {
// your mocking services
};
});
// removes context from global namespace after each test
afterEach(() => {
delete global.context;
});
test("should perform operation using App Services globals", () => {
// test function that uses context
});

コンテキストにアクセスする関数のモック作成

この例の関数は、App Services 値にアクセスし、それを返します。

helloWithValue.js
function greet() {
const greeting = context.values.get("greeting"); // the greeting is 'beautiful world'
return "hello " + greeting;
}
exports = greet;
if (typeof module !== "undefined") {
module.exports = greet;
}

テスト ファイルhelloWithValue.test.jsを作成します。 テストファイルには、次のものが含まれています。

  • helloWithValue.jsからエクスポートされた関数をインポートします。

  • context.values.get()のモック。 グローバル名前空間を破損しないようにブロックをセットアップします。

  • モックを使用するインポートされた関数のテスト。

helloWithValue.test.js
// import the function
const greet = require("../../functions/helloWithValue");
// wrap the mock in beforeEach/afterEach blocks to avoid
// pollution of the global namespace
beforeEach(() => {
// mock of context.values.get()
global.context = {
values: {
get: (val) => {
const valsMap = {
greeting: "magnificent morning",
};
return valsMap[val];
},
},
};
});
afterEach(() => {
// delete the mock to not pollute global namespace
delete global.context;
});
// test function using mock
test("should greet with value", () => {
const greeting = greet();
expect(greeting).toBe("hello magnificent morning");
});

すべての関数は、本番環境にデプロイする前に、統合テストを実行する必要があります。 Atlas Function JavaScript ランタイムは標準の Node.js ランタイムと異なるため、これは特に重要です。 App Services に配置された関数をテストしないと、予期しないエラーが発生する可能性があります。

関数の統合テストを記述するには単一の方法はありません。 関数は異なる目的でさまざまなコンテキストで使用できるため、それぞれのユースケースには異なる統合テスト戦略が必要です。

たとえば、Device SDK クライアントから呼び出す関数の統合テストを作成する方法は、データベースtrigger関数をテストする方法とは異なります。

ただし、 関数 の統合テストを作成するには一般的な手順がいくつかあります。 大まかレベルでは、これらの手順は次のようになります。

  1. 本番アプリと同じ構成でテスト アプリを作成します。

  2. ライブ テスト環境に配置された関数を操作する統合テストを作成します。

このセクションの残りの部分では、アプリに 統合テスト を実装する方法について、より詳細に説明します。

Tip

以下も参照してください。

関数 JavaScript ランタイムの固有の要素の詳細については、以下を参照してください。

関数のさまざまなユースケースの詳細については、 「 関数を使用する場合 」を参照してください。

1

データソースとバックエンド構成が異なることを除いて、本番アプリと同じ構成を持つテスト目的のアプリを作成します。

同じ構成を持つ複数のアプリを作成する方法の詳細については、「アプリ環境の構成 」を参照してください。

2

テスト アプリを配置したら、お好みのテスト言語とフレームワークを使用してその機能をテストします。

:ref:Realm クライアント SDK は、アプリをテストするのに役立ちます。 これらの SDK は、App Services へのファーストクラスのアクセスを提供します。 テスト スイートでは、Realm SDK を使用してテスト アプリに接続できます。 Realm SDK を使用して、アプリとの相互作用をテストします。

データベースtrigger関数のテスト

この例では、Realm Node.js SDKと Jest テスト フレームワークを使用して、データベース trigger をテストします。

trigger 関数は マテリアライズドビュー を作成します 新しい販売が行われるたびに、製品の合計売上のが増加します。

エントリがsalesテーブルに追加されるたびに trigger が起動します。 total_sales_materializedテーブルのtotal_salesフィールドが 1 インクリメントされます。

データベースtriggerの構成は次のとおりです。

triggers/materializeTotalSales.json
{
"id": "62bb0d9f852c6e062432c454",
"name": "materializeTotalSales",
"type": "DATABASE",
"config": {
"operation_types": ["INSERT"],
"database": "store",
"collection": "sales",
"service_name": "mongodb-atlas",
"match": {},
"project": {},
"full_document": true,
"full_document_before_change": false,
"unordered": false,
"skip_catchup_events": false
},
"disabled": false,
"event_processors": {
"FUNCTION": {
"config": {
"function_name": "materializeTotalSales"
}
}
}
}

trigger は次の関数を呼び出します。

functions/materializeTotalSales.js
exports = function (changeEvent) {
const {
fullDocument: { productId },
} = changeEvent;
const totalSalesMaterialization = context.services
.get("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
totalSalesMaterialization.updateOne(
{ _id: productId },
{ $inc: { total_sales: 1 } },
{ upsert: true }
);
};

この例では、 Node.js Realm SDKを使用して trigger をテストし、MongoDB Atlas と対話します。 また、Realm MongoDBQueryAPI またはMongoDB ドライバー のいずれかを持つ任意の SDKMongoDB Atlas を使用して、 をクエリし、 データベースtrigger をテストすることもできます。

test/integration/materializeTotalSales.test.js
const { app_id } = require("../../root_config.json");
const Realm = require("realm");
const { BSON } = require("realm");
let user;
const app = new Realm.App(app_id);
const sandwichId = BSON.ObjectId();
const saladId = BSON.ObjectId();
// utility function
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
// Set up. Creates and logs in a user, which you need to query MongoDB Atlas
// with the Realm Node.js SDK
beforeEach(async () => {
const credentials = Realm.Credentials.anonymous();
user = await app.logIn(credentials);
});
// Clean up. Removes user and data created in the test.
afterEach(async () => {
const db = user.mongoClient("mongodb-atlas").db("store");
await db.collection("sales").deleteMany({});
await db.collection("total_sales_materialized").deleteMany({});
await app.deleteUser(user);
});
test("Trigger creates a new materialization", async () => {
const sales = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("sales");
await sales.insertOne({
_id: BSON.ObjectId(),
productId: sandwichId,
price: 12.0,
timestamp: Date.now(),
});
// give time for the Trigger to execute on Atlas
await sleep(1000);
const totalSalesMaterialized = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
const allSandwichSales = await totalSalesMaterialized.findOne({
_id: sandwichId,
});
// checks that Trigger increments creates and increments total_sales
expect(allSandwichSales.total_sales).toBe(1);
});
test("Trigger updates an existing materialization", async () => {
const sales = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("sales");
await sales.insertOne({
_id: BSON.ObjectId(),
productId: saladId,
price: 15.0,
timestamp: Date.now(),
});
await sales.insertOne({
_id: BSON.ObjectId(),
productId: saladId,
price: 15.0,
timestamp: Date.now(),
});
// give time for Trigger to execute on Atlas
await sleep(1000);
const totalSalesMaterialized = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
const allSaladSales = await totalSalesMaterialized.findOne({
_id: saladId,
});
// checks that Trigger increments total_sales for each sale
expect(allSaladSales.total_sales).toBe(2);
});

戻る

外部依存関係