Esta página describe algunas estrategias que puedes utilizar para probar tus Funciones Atlas.
Due to differences between the Functions JavaScript runtime and the standard Node.js runtime, you must take some unique considerations into account when testing Functions. This page covers how to handle the uniqueness of Functions.
Antes de comenzar
Necesitará lo siguiente para probar una función Atlas:
An Atlas App Services App. To learn how to create one, see Create an App.
A code deploy method to configure your App. Choose one of:
Una copia de la CLI de App Services instalada y agregada a su sistema local
PATHPara saber cómo hacerlo,consulte Instalar la CLI de App Services.Un repositorio de GitHub configurado para almacenar e implementar archivos de configuración para tu aplicación. Para saber cómo configurarlo, consulta Implementar automáticamente con GitHub.
Pruebas unitarias para funciones
Puedes validar la funcionalidad de tus funciones con pruebas unitarias. Usa cualquier framework de pruebas compatible con Node.js para probar las funciones. Los ejemplos de esta página utilizan... Marco de prueba Jest.
Debe utilizar módulos CommonJS para escribir pruebas unitarias para funciones.
Crear una nueva función
Crea una nueva función. En los archivos de configuración de la aplicación, crea un nuevo archivo JavaScript para tu función en el directorio functions.
touch functions/hello.js
You also need to add configuration information for the Function to functions/config.json.
{ "name": "hello", "private": false, "run_as_system": true },
Tip
Para obtener más información sobre cómo crear una nueva función, consulte Definir una función.
Write Function code
To make your Function code easy to test, keep it modular by separating its concerns into distinct components. You must keep all logic for the Function in the file you defined in the previous step. You cannot perform relative imports from other files in your project in a Function file. You can also import dependencies using npm.
You must export your function by assigning it to exports.
function greet(word) { return "hello " + word; } function greetWithPunctuation(word, punctuation) { return greet(word) + punctuation; } // Function exported to App Services exports = greetWithPunctuation;
Función de exportación para uso en pruebas unitarias
Para exportar su código para usarlo en archivos de pruebas unitarias Node.js separados, debe usar la sintaxis CommonJS module.exports.
This syntax is not compatible with the Functions runtime. The Atlas Functions environment does not provide the Node.js global module. To export modules to your unit tests while keeping the file compatible with Functions, wrap the the module.exports statement with a check to see if the global module object exists.
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 }; }
Unit test exported Function code
Now you can write unit tests for the modules that you exported from the Function file. Create a test file for the Function file in a separate test directory somewhere in your project.
mkdir -p test/unit touch test/unit/hello.test.js
Importa los módulos que exportaste en el paso anterior y agrega pruebas unitarias.
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!!!"); });
Mock Services
To write unit tests for Functions that use the global context object or one of the other global modules that Functions expose, you must create mocks of their behavior.
In this example, the Function references an App Services Value via context.values.get() and creates an ObjectId using the global module BSON.
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; }
Adjunte estos mocks al espacio de nombres global de Node.js. Esto le permite llamarlos en sus pruebas unitarias de la misma forma que en el entorno de ejecución de Functions.
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 }
You may also want to declare and remove these mocks in setup and teardown blocks so that they do not pollute the global namespace.
// 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 });
Ejemplo
Burlarse de una función que accede al contexto
La función de este ejemplo accede a un valor de App Services y lo devuelve.
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; }
Ahora crea un archivo de prueba helloWithValue.test.js. El archivo de prueba contiene lo siguiente:
Importar la función exportada desde
helloWithValue.js.Una simulación de
context.values.get(). Envuelva la simulación en bloques de configuración y desmontaje para que no contamine el espacio de nombres global.A test of the imported function that uses the mock.
// 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"); });
Pruebas de integración para funciones
You should perform integration tests on all Functions before deploying them to production environments. This is especially important because the Atlas Function JavaScript runtime differs from the standard Node.js runtime. Unexpected errors can occur if you do not test functions deployed to App Services.
There is no single way to write integration tests for Functions. As Functions can be used in a variety of different contexts for different purposes, each use case requires a different integration testing strategy.
For example, the way you create an integration test for a Function that you invoke from a Device SDK client is different from the way you would test a Database Trigger Function.
However, there are some general steps that you can take to writing integration tests for Functions. On a high level these steps are:
Create a testing App with the same configuration as your production App.
Escriba pruebas de integración que interactúen con sus funciones implementadas en un entorno de prueba en vivo.
The remainder of this section explains how to implement integration tests for your App in more detail.
Tip
For more information on the unique aspects of the Functions JavaScript runtime, refer to:
Para obtener más información sobre los diferentes casos de uso de Functions, consulta Cuándo usar Functions.
Create a test App
Cree una aplicación para fines de prueba que tenga la misma configuración que su aplicación de producción, excepto que utilice diferentes fuentes de datos y configuración de backend.
Para obtener más información sobre cómo crear varias aplicaciones con la misma configuración, consulta Configurar un entorno de aplicación.
Prueba en entorno real
Una vez que haya implementado su aplicación de prueba, pruebe su funcionalidad utilizando su lenguaje y marco de prueba preferidos.
Los SDK de cliente de Realm son útiles para probar aplicaciones. Estos SDK proporcionan acceso de primera clase a App Services. En tu suite de pruebas, puedes conectarte a tu aplicación de prueba con un SDK de Realm. Prueba la interacción con la aplicación usando el SDK de Realm.
Ejemplo
Testing a Database Trigger Function
This example uses the Realm Node.js SDK and the Jest testing framework to test a Database Trigger.
La función activadora crea una vista materializada de las ventas totales de un producto cada vez que se realiza una nueva venta.
El activador se activa cada vez que se agrega una entrada a la tabla sales. Incrementa el campo total_sales en la tabla total_sales_materialized en uno.
El disparador de base de datos tiene la siguiente configuración:
{ "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" } } } }
The Trigger invokes the following Function:
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 } ); };
This example tests the Trigger using the Node.js Realm SDK to interact with MongoDB Atlas. You can also use any Realm SDK with the MongoDB Query API or one of the MongoDB drivers to query MongoDB Atlas to test a Database Trigger.
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); });