Модульное тестирование частных функций с моккой и node.js

Я использую mocha для unit test приложения, написанного для node.js

Интересно, возможно ли использовать функции unit test, которые не были экспортированы в модуле.

Пример:

У меня есть много функций, определенных как в foobar.js

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

и несколько функций экспортируются как общедоступные:

exports.public_foobar3 = function(){
    ...
}

Тестовый пример структурирован следующим образом:

describe("private_foobar1", function() {
    it("should do stuff", function(done) {
        var stuff = foobar.private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

Очевидно, это не работает, поскольку private_foobar1 не экспортируется.

Каков правильный способ индивидуального тестирования частных методов? Мокка имеет встроенные методы для этого?

Ответ 1

Если функция не экспортируется модулем, она не может быть вызвана тестовым кодом вне модуля. Это из-за того, как работает JavaScript, и Мокка сама по себе не может обойти это.

В тех немногих случаях, когда я решил, что проверка частной функции - это правильная вещь, я выполнил некоторую переменную среды, которую мой модуль проверяет, будет ли он запущен в тестовой настройке или нет. Если он запускается в тестовой настройке, он экспортирует дополнительные функции, которые я могу вызвать во время тестирования.

Слово "среда" здесь свободно используется. Это может означать проверку process.env или что-то еще, которое может связываться с модулем, который вы сейчас тестируете. Экземпляры, где я должен был это сделать, были в среде RequireJS, и я использовал module.config для этой цели.

Ответ 2

Откройте rewire модуль. Это позволяет вам (и манипулировать) частными переменными и функциями внутри модуля.

Итак, в вашем случае использование будет выглядеть примерно так:

var rewire = require('rewire'),
    foobar = rewire('./foobar'); // Bring your module in with rewire

describe("private_foobar1", function() {

    // Use the special '__get__' accessor to get your private function.
    var private_foobar1 = foobar.__get__('private_foobar1');

    it("should do stuff", function(done) {
        var stuff = private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

Ответ 3

Вот действительно хороший рабочий процесс для тестирования ваших личных методов, который объясняет Филипп Уолтон, инженер Google из своего блога.

Принцип

  • Напиши свой код нормально
  • Свяжите свои частные методы с объектом в отдельном блоке кода, отметьте его, например, _
  • Окружите этот блок кода начальными и конечными комментариями

Затем используйте задачу сборки или собственную систему сборки (например, код grunt-strip-code), чтобы удалить этот блок для производственных сборок.

Ваши тестовые сборки имеют доступ к вашему частному API, а ваши производственные - нет.

отрывок

Напишите свой код как это:

var myModule = (function() {

  function foo() {
    // private function 'foo' inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function 'bar' returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

И ваши грубые задачи, как это

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

Глубже

В более поздней статье это объясняет "почему" "тестирования частных методов"

Ответ 4

Если вы предпочитаете, чтобы все было просто, просто экспортируйте также приватные элементы, но четко отделив их от общедоступного API с некоторыми соглашениями, например, добавьте к ним префикс _ или вложите их в один частный объект.

var privateWorker = function() {
    return 1
}

var doSomething = function() {
    return privateWorker()
}

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}

Ответ 5

Для этого я создал пакет npm, который может оказаться полезным: require-from

В основном вы выставляете закрытые методы:

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}

примечание: testExports может быть любым допустимым именем, кроме exports конечно.

И из другого модуля:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;

Ответ 6

Я добавил дополнительную функцию, которую я назвал Internal() и возвращает оттуда все частные функции. Эта функция Internal() затем экспортируется. Пример:

function Internal () {
  return { Private_Function1, Private_Function2, Private_Function2}
}

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

Вы можете вызвать внутренние функции следующим образом:

let test = require('.....')
test.Internal().Private_Function1()

Мне больше нравится это решение, потому что:

  • всегда экспортируется только одна функция Internal(). Эта функция Internal() всегда используется для проверки приватных функций.
  • Это просто реализовать
  • Низкое влияние на производственный код (только одна дополнительная функция)

Ответ 7

Я выполнил ответ @barwin и проверил, как модульные тесты могут быть выполнены с помощью модуля rewire. Я могу подтвердить, что это решение просто работает.

Модуль должен быть необходим в двух частях - общедоступном и частном. Для публичных функций вы можете сделать это стандартным способом:

const { public_foobar3 } = require('./foobar');

Для частного использования:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

Чтобы узнать больше о предмете, я создал рабочий пример с полным тестированием модулей, тестирование включает в себя частную и общедоступную область.

Для получения дополнительной информации я рекомендую вам проверить статью (https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f), полностью описывая тему, она включает образцы кода.

Ответ 8

Я знаю, что это не обязательно тот ответ, который вы ищете, но я обнаружил, что большую часть времени, если частная функция заслуживает тестирования, она стоит в своем собственном файле.

Например, вместо использования закрытых методов в том же файле, что и общедоступные, как это...

ЦСИ/вещь/PublicInterface.js


function helper1 (x) {
    return 2 * x;
}

function helper2 (x) {
    return 3 * x;
}

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

... ты разделил это так:

ЦСИ/вещь/PublicInterface.js

import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

ЦСИ/вещь/внутренняя /helper1.js

export function helper1 (x) {
    return 2 * x;
}

ЦСИ/вещь/внутренняя /helper2.js

export function helper2 (x) {
    return 3 * x;
}

Таким образом, вы можете легко протестировать helper1 и helper2 как есть, без использования Rewire и другой "магии" (у которой, как я обнаружил, есть свои болевые точки при отладке или при попытке перейти к TypeScript, а не к упомянуть о худшей понятности для новых коллег). А то, что они находятся в подпапке с internal именем или чем-то в этом роде, поможет избежать их случайного использования в непредусмотренных местах.


PS: Еще одна распространенная проблема с "закрытыми" методами заключается в том, что если вы хотите протестировать publicMethod1 и publicMethod2 и высмеивать помощников, то вам, как правило, для этого требуется что-то вроде Rewire. Однако, если они находятся в отдельных файлах, вы можете использовать для этого Proxyquire, который, в отличие от Rewire, не требует каких-либо изменений в процессе сборки, легко читается и отлаживается и хорошо работает даже с TypeScript.

Ответ 9

Чтобы сделать частные методы доступными для тестирования, я делаю это:

const _myPrivateMethod: () => {};

const methods = {
    myPublicMethod1: () => {},
    myPublicMethod2: () => {},
}

if (process.env.NODE_ENV === 'test') {
    methods._myPrivateMethod = _myPrivateMethod;
}

module.exports = methods;