Pruebas unitarias en complementos de Office

Las pruebas unitarias comprueban la funcionalidad del complemento sin necesidad de conexiones de red o servicio, incluidas las conexiones a la aplicación de Office. El código del lado servidor de pruebas unitarias y el código del lado cliente que no llama a las API de JavaScript de Office son los mismos en los complementos de Office que en cualquier aplicación web, por lo que no requiere documentación especial. Sin embargo, el código del lado cliente que llama a las API de JavaScript de Office es difícil de probar. Para solucionar estos problemas, hemos creado una biblioteca para simplificar la creación de objetos ficticios de Office en pruebas unitarias: Office-Addin-Mock. La biblioteca facilita las pruebas de las siguientes maneras:

  • Las API de JavaScript de Office deben inicializarse en un control de vista web en el contexto de una aplicación de Office (Excel, Word, etc.), por lo que no se pueden cargar en el proceso en el que se ejecutan pruebas unitarias en el equipo de desarrollo. La biblioteca Office-Addin-Mock se puede importar en los archivos de prueba, lo que permite simular las API de JavaScript de Office dentro del proceso de Node.js en el que se ejecutan las pruebas.
  • Las API específicas de la aplicación tienen métodos de carga y sincronización a los que se debe llamar en un orden determinado en relación con otras funciones y entre sí. Además, se debe llamar al load método con determinados parámetros en función de las propiedades de los objetos de Office que se van a leer mediante código más adelante en la función que se va a probar. Pero los marcos de pruebas unitarias son intrínsecamente sin estado, por lo que no pueden mantener un registro de si load se llamó o sync se llamó a o a qué parámetros se pasaron a load. Los objetos ficticios que se crean con la biblioteca Office-Addin-Mock tienen un estado interno que realiza un seguimiento de estas cosas. Esto permite que los objetos ficticios emulan el comportamiento de error de los objetos de Office reales. Por ejemplo, si la función que se está probando intenta leer una propiedad que no se pasó primero a load, la prueba devolverá un error similar al que devolvería Office.

La biblioteca no depende de las API de JavaScript de Office y se puede usar con cualquier marco de pruebas unitarias de JavaScript, como:

En los ejemplos de este artículo se usa el marco Jest. Hay ejemplos que usan el marco mocha en la página principal Office-Addin-Mock.

Requisitos previos

En este artículo se supone que está familiarizado con los conceptos básicos de pruebas unitarias y simulación, incluido cómo crear y ejecutar archivos de prueba, y que tiene cierta experiencia con un marco de pruebas unitarias.

Sugerencia

Si trabaja con Visual Studio, le recomendamos que lea el artículo Pruebas unitarias de JavaScript y TypeScript en Visual Studio para obtener información básica sobre las pruebas unitarias de JavaScript en Visual Studio y, a continuación, volver a este artículo.

Instalación de la herramienta

Para instalar la biblioteca, abra un símbolo del sistema, vaya a la raíz del proyecto de complemento y escriba el siguiente comando.

npm install office-addin-mock --save-dev

Uso básico

  1. El proyecto tendrá uno o varios archivos de prueba. (Consulte las instrucciones para el marco de pruebas y los archivos de prueba de ejemplo en Ejemplos a continuación). Importe la biblioteca, con la require palabra clave o import , a cualquier archivo de prueba que tenga una prueba de una función que llame a las API de JavaScript de Office, como se muestra en el ejemplo siguiente.

    const OfficeAddinMock = require("office-addin-mock");
    
  2. Importe el módulo que contiene la función de complemento que desea probar con la require palabra clave o import . A continuación se muestra un ejemplo que supone que el archivo de prueba está en una subcarpeta de la carpeta con los archivos de código del complemento.

    const myOfficeAddinFeature = require("../my-office-add-in");
    
  3. Cree un objeto de datos que tenga las propiedades y subpropiedades que necesita simular para probar la función. A continuación se muestra un ejemplo de un objeto que simula la propiedad Workbook.range.address de Excel y el método Workbook.getSelectedRange . Este no es el objeto ficticio final. Piense en él como un objeto de inicialización que se usa OfficeMockObject para crear el objeto ficticio final.

    const mockData = {
      workbook: {
        range: {
          address: "C2:G3",
        },
        getSelectedRange: function () {
          return this.range;
        },
      },
    };
    
  4. Pase el objeto de datos al OfficeMockObject constructor. Tenga en cuenta lo siguiente sobre el objeto devuelto OfficeMockObject .

    • Es una simulación simplificada de un objeto OfficeExtension.ClientRequestContext .
    • El objeto ficticio tiene todos los miembros del objeto de datos y también tiene implementaciones ficticias de los load métodos y sync .
    • El objeto ficticio imitará el comportamiento de error crucial del ClientRequestContext objeto. Por ejemplo, si la API de Office que está probando intenta leer una propiedad sin cargar primero la propiedad y llamar a sync, la prueba producirá un error similar al que se produciría en tiempo de ejecución de producción: "Error, propiedad no cargada".
    const contextMock = new OfficeAddinMock.OfficeMockObject(mockData);
    

    Nota:

    La documentación de referencia completa del OfficeMockObject tipo se encuentra en Office-Addin-Mock.

  5. En la sintaxis del marco de prueba, agregue una prueba de la función. Utilice el OfficeMockObject objeto en lugar del objeto del que se simula, en este caso el ClientRequestContext objeto . A continuación, se continúa el ejemplo en Jest. En esta prueba de ejemplo se supone que la función de complemento que se está probando se denomina getSelectedRangeAddress, que toma un ClientRequestContext objeto como parámetro y que está pensado para devolver la dirección del intervalo seleccionado actualmente. El ejemplo completo es más adelante en este artículo.

    test("getSelectedRangeAddress should return the address of the range", async function () {
      expect(await getSelectedRangeAddress(contextMock)).toBe("C2:G3");
    });
    
  6. Ejecute la prueba de acuerdo con la documentación del marco de pruebas y las herramientas de desarrollo. Normalmente, hay un archivo package.json con un script que ejecuta el marco de prueba. Por ejemplo, si Jest es el marco, package.json contendrá lo siguiente:

    "scripts": {
      "test": "jest",
      -- other scripts omitted --  
    }
    

    Para ejecutar la prueba, escriba lo siguiente en un símbolo del sistema en la raíz del proyecto.

    npm test
    

Ejemplos

En los ejemplos de esta sección se usa Jest con su configuración predeterminada. Esta configuración admite módulos CommonJS. Consulte la documentación de Jest para ver cómo configurar Jest y Node.js para admitir módulos ECMAScript y para admitir TypeScript. Para ejecutar cualquiera de estos ejemplos, siga estos pasos.

  1. Cree un proyecto de complemento de Office para la aplicación host de Office adecuada (por ejemplo, Excel o Word). Una manera de hacerlo rápidamente es usar el generador de Yeoman para complementos de Office.
  2. En la raíz del proyecto, instale Jest.
  3. Instale la herramienta office-addin-mock.
  4. Cree un archivo exactamente igual que el primer archivo del ejemplo y agréguelo a la carpeta que contiene los otros archivos de origen del proyecto, a menudo denominado \src.
  5. Cree una subcarpeta en la carpeta de archivos de origen y asígnele un nombre adecuado, como \tests.
  6. Cree un archivo exactamente igual que el archivo de prueba en el ejemplo y agréguelo a la subcarpeta .
  7. Agregue un test script al archivo package.json y, a continuación, ejecute la prueba, como se describe en Uso básico.

Simulación de las API comunes de Office

En este ejemplo se supone que hay un complemento de Office para cualquier host que admita las API comunes de Office (por ejemplo, Excel, PowerPoint o Word). El complemento tiene una de sus características en un archivo denominado my-common-api-add-in-feature.js. A continuación se muestra el contenido del archivo. La addHelloWorldText función establece el texto "Hola mundo!" en lo que esté seleccionado actualmente en el documento; por ejemplo, un rango en Word, una celda en Excel o un cuadro de texto en PowerPoint.

const myCommonAPIAddinFeature = {

    addHelloWorldText: async () => {
        const options = { coercionType: Office.CoercionType.Text };
        await Office.context.document.setSelectedDataAsync("Hello World!", options);
    }
}
  
module.exports = myCommonAPIAddinFeature;

El archivo de prueba, denominado my-common-api-add-in-feature.test.js está en una subcarpeta, en relación con la ubicación del archivo de código del complemento. A continuación se muestra el contenido del archivo. Tenga en cuenta que la propiedad de nivel superior es context, un objeto Office.Context , por lo que el objeto que se está simulando es el elemento primario de esta propiedad: un objeto de Office . Tenga en cuenta lo siguiente en relación con este código:

  • El OfficeMockObject constructor no agrega todas las clases de enumeración de Office al objeto ficticio Office , por lo que el CoercionType.Text valor al que se hace referencia en el método del complemento debe agregarse explícitamente en el objeto de inicialización.
  • Dado que la biblioteca de JavaScript de Office no se carga en el proceso de nodo, el Office objeto al que se hace referencia en el código del complemento debe declararse e inicializarse.
const OfficeAddinMock = require("office-addin-mock");
const myCommonAPIAddinFeature = require("../my-common-api-add-in-feature");

// Create the seed mock object.
const mockData = {
    context: {
      document: {
        setSelectedDataAsync: function (data, options) {
          this.data = data;
          this.options = options;
        },
      },
    },
    // Mock the Office.CoercionType enum.
    CoercionType: {
      Text: {},
    },
};
  
// Create the final mock object from the seed object.
const officeMock = new OfficeAddinMock.OfficeMockObject(mockData);

// Create the Office object that is called in the addHelloWorldText function.
global.Office = officeMock;

/* Code that calls the test framework goes below this line. */

// Jest test
test("Text of selection in document should be set to 'Hello World'", async function () {
    await myCommonAPIAddinFeature.addHelloWorldText();
    expect(officeMock.context.document.data).toBe("Hello World!");
});

Simulación de las API de Outlook

Aunque en términos estrictos, las API de Outlook forman parte del modelo de API común, tienen una arquitectura especial que se basa en el objeto Mailbox , por lo que hemos proporcionado un ejemplo distinto para Outlook. En este ejemplo se supone que Outlook tiene una de sus características en un archivo denominado my-outlook-add-in-feature.js. A continuación se muestra el contenido del archivo. La addHelloWorldText función establece el texto "Hola mundo!" en lo que esté seleccionado actualmente en la ventana de redacción del mensaje.

const myOutlookAddinFeature = {

    addHelloWorldText: async () => {
        Office.context.mailbox.item.setSelectedDataAsync("Hello World!");
      }
}

module.exports = myOutlookAddinFeature;

El archivo de prueba, denominado my-outlook-add-in-feature.test.js está en una subcarpeta, en relación con la ubicación del archivo de código del complemento. A continuación se muestra el contenido del archivo. Tenga en cuenta que la propiedad de nivel superior es context, un objeto Office.Context , por lo que el objeto que se está simulando es el elemento primario de esta propiedad: un objeto de Office . Tenga en cuenta lo siguiente en relación con este código:

  • La host biblioteca simulada usa internamente la propiedad del objeto ficticio para identificar la aplicación de Office. Es obligatorio para Outlook. Actualmente no tiene ningún propósito para ninguna otra aplicación de Office.
  • Dado que la biblioteca de JavaScript de Office no se carga en el proceso de nodo, el Office objeto al que se hace referencia en el código del complemento debe declararse e inicializarse.
const OfficeAddinMock = require("office-addin-mock");
const myOutlookAddinFeature = require("../my-outlook-add-in-feature");

// Create the seed mock object.
const mockData = {
  // Identify the host to the mock library (required for Outlook).
  host: "outlook",
  context: {
    mailbox: {
      item: {
          setSelectedDataAsync: function (data) {
          this.data = data;
        },
      },
    },
  },
};
  
// Create the final mock object from the seed object.
const officeMock = new OfficeAddinMock.OfficeMockObject(mockData);

// Create the Office object that is called in the addHelloWorldText function.
global.Office = officeMock;

/* Code that calls the test framework goes below this line. */

// Jest test
test("Text of selection in message should be set to 'Hello World'", async function () {
    await myOutlookAddinFeature.addHelloWorldText();
    expect(officeMock.context.mailbox.item.data).toBe("Hello World!");
});

Simulación de las API específicas de la aplicación de Office

Al probar funciones que usan las API específicas de la aplicación, asegúrese de que se está burlando del tipo correcto de objeto. Hay dos opciones:

  • Simular un Objeto OfficeExtension.ClientRequestObject. Haga esto cuando la función que se está probando cumpla las dos condiciones siguientes:

    • No llama a un host.run como Excel.run.
    • No hace referencia a ninguna otra propiedad o método directo de un objeto Host .
  • Simular un objeto Host, como Excel o Word. Haga esto cuando no sea posible la opción anterior.

Los ejemplos de ambos tipos de pruebas se encuentran en las subsecciones siguientes.

Nota:

La biblioteca Office-Addin-Mock no admite actualmente objetos de tipo de colección ficticios, que son todos los objetos de las API específicas de la aplicación que se denominan en el patrón *Collection, como WorksheetCollection. Estamos trabajando duro para agregar esta compatibilidad a la biblioteca.

Simulación de un objeto ClientRequestContext

En este ejemplo se supone que un complemento de Excel que tiene una de sus características en un archivo denominado my-excel-add-in-feature.js. A continuación se muestra el contenido del archivo. Tenga en cuenta que getSelectedRangeAddress es un método auxiliar llamado dentro de la devolución de llamada que se pasa a Excel.run.

const myExcelAddinFeature = {
    
    getSelectedRangeAddress: async (context) => {
        const range = context.workbook.getSelectedRange();      
        range.load("address");

        await context.sync();
      
        return range.address;
    }
}

module.exports = myExcelAddinFeature;

El archivo de prueba, denominado my-excel-add-in-feature.test.js está en una subcarpeta, en relación con la ubicación del archivo de código del complemento. A continuación se muestra el contenido del archivo. Tenga en cuenta que la propiedad de nivel superior es workbook, por lo que el objeto que se está simulando es el elemento primario de : Excel.Workbookun ClientRequestContext objeto .

const OfficeAddinMock = require("office-addin-mock");
const myExcelAddinFeature = require("../my-excel-add-in-feature");

// Create the seed mock object.
const mockData = {
    workbook: {
      range: {
        address: "C2:G3",
      },
      // Mock the Workbook.getSelectedRange method.
      getSelectedRange: function () {
        return this.range;
      },
    },
};

// Create the final mock object from the seed object.
const contextMock = new OfficeAddinMock.OfficeMockObject(mockData);

/* Code that calls the test framework goes below this line. */

// Jest test
test("getSelectedRangeAddress should return address of selected range", async function () {
  expect(await myOfficeAddinFeature.getSelectedRangeAddress(contextMock)).toBe("C2:G3");
});

Simulación de un objeto host

En este ejemplo se supone que un complemento de Word que tiene una de sus características en un archivo denominado my-word-add-in-feature.js. A continuación se muestra el contenido del archivo.

const myWordAddinFeature = {

  insertBlueParagraph: async () => {
    return Word.run(async (context) => {
      // Insert a paragraph at the end of the document.
      const paragraph = context.document.body.insertParagraph("Hello World", Word.InsertLocation.end);
  
      // Change the font color to blue.
      paragraph.font.color = "blue";
  
      await context.sync();
    });
  }
}

module.exports = myWordAddinFeature;

El archivo de prueba, denominado my-word-add-in-feature.test.js está en una subcarpeta, en relación con la ubicación del archivo de código del complemento. A continuación se muestra el contenido del archivo. Tenga en cuenta que la propiedad de nivel superior es context, un ClientRequestContext objeto , por lo que el objeto que se está simulando es el elemento primario de esta propiedad: un Word objeto . Tenga en cuenta lo siguiente en relación con este código:

  • Cuando el OfficeMockObject constructor crea el objeto ficticio final, se asegurará de que el objeto secundario ClientRequestContext tenga sync métodos y load .
  • El OfficeMockObject constructor no agrega una run función al objeto ficticio Word , por lo que debe agregarse explícitamente en el objeto de inicialización.
  • El OfficeMockObject constructor no agrega todas las clases de enumeración de Word al objeto ficticioWord, por lo que el InsertLocation.end valor al que se hace referencia en el método del complemento debe agregarse explícitamente en el objeto de inicialización.
  • Dado que la biblioteca de JavaScript de Office no se carga en el proceso de nodo, el Word objeto al que se hace referencia en el código del complemento debe declararse e inicializarse.
const OfficeAddinMock = require("office-addin-mock");
const myWordAddinFeature = require("../my-word-add-in-feature");

// Create the seed mock object.
const mockData = {
  context: {
    document: {
      body: {
        paragraph: {
          font: {},
        },
        // Mock the Body.insertParagraph method.
        insertParagraph: function (paragraphText, insertLocation) {
          this.paragraph.text = paragraphText;
          this.paragraph.insertLocation = insertLocation;
          return this.paragraph;
        },
      },
    },
  },
  // Mock the Word.InsertLocation enum.
  InsertLocation: {
    end: "end",
  },
  // Mock the Word.run function.
  run: async function(callback) {
    await callback(this.context);
  },
};

// Create the final mock object from the seed object.
const wordMock = new OfficeAddinMock.OfficeMockObject(mockData);

// Define and initialize the Word object that is called in the insertBlueParagraph function.
global.Word = wordMock;

/* Code that calls the test framework goes below this line. */

// Jest test set
describe("Insert blue paragraph at end tests", () => {

  test("color of paragraph", async function () {
    await myWordAddinFeature.insertBlueParagraph();  
    expect(wordMock.context.document.body.paragraph.font.color).toBe("blue");
  });

  test("text of paragraph", async function () {
    await myWordAddinFeature.insertBlueParagraph();
    expect(wordMock.context.document.body.paragraph.text).toBe("Hello World");
  });
})

Nota:

La documentación de referencia completa del OfficeMockObject tipo se encuentra en Office-Addin-Mock.

Vea también