Esta página describe algunas estrategias que puede utilizar para probar sus 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:
Una aplicación de Atlas App Services. Para saber cómo crear una, consulte Crear una aplicación.
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
Para facilitar la prueba del código de su función, manténgalo modular separando sus componentes en distintos componentes. Debe conservar toda la lógica de la función en el archivo definido en el paso anterior. No puede realizar importaciones relativas desde otros archivos de su proyecto en un archivo de función. También puede importar dependencias mediante 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.
Esta sintaxis no es compatible con el entorno de ejecución de Functions. El entorno de Atlas Functions no proporciona el objeto global module de Node.js. Para exportar módulos a las pruebas unitarias y mantener la compatibilidad del archivo con Functions, incluya la instrucción module.exports con una comprobación para comprobar si existe el objeto global module.
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 }; }
Código de función exportado de prueba unitaria
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.
No existe una única forma de escribir pruebas de integración para funciones. Dado que las funciones pueden utilizarse en diversos contextos para distintos propósitos, cada caso de uso requiere una estrategia de pruebas de integración distinta.
Por ejemplo, la forma en que crea una prueba de integración para una función que invoca desde un cliente SDK de dispositivo es diferente de la forma en que probaría una función de activación de base de datos.
However, there are some general steps that you can take to writing integration tests for Functions. On a high level these steps are:
Cree una aplicación de prueba con la misma configuración que su aplicación de producción.
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 las funciones, consulte Cuándo usar funciones.
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,consulte 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 } ); };
Este ejemplo prueba el disparador usando el SDK de Realm de Node.js para interactuar con MongoDB Atlas. También puede usar cualquier SDK de Realm con la API de consulta de MongoDB o uno de los controladores de MongoDB para consultar MongoDB Atlas y probar un disparador de base de datos.
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); });