使用Jest对Angular Directives进行单元测试

时间:2017-03-30 06:56:40

标签: angularjs jestjs angular-mock

我觉得我在这个极其简化的角度指令单元测试中缺少一些至关重要的东西:

import * as angular from 'angular'
import 'angular-mocks'

const app = angular.module('my-app', [])

app.directive('myDirective', () => ({
    template: 'this does not work either',
    link: (scope, element) => { // have also tried compile fn
        console.log('This does not log')
        element.html('Hi!')
    }
}))

describe('myDirective', () => {
    var element, scope

    beforeEach(app)

    beforeEach(inject(($rootScope, $compile) => {
        scope = $rootScope.$new()
        element = $compile('<my-directive />')(scope)
        scope.$digest()
    }))

    it('should actually do something', () => {
        expect(element.html()).toEqual('Hi!')
    })
})

当jest运行时,似乎指令尚未链接/编译/无论

 FAIL  test/HtmlToPlaintextDirective.spec.js
  ● myDirective › should actually do something

    expect(received).toEqual(expected)

    Expected value to equal:
      "Hi!"
    Received:
      ""

2 个答案:

答案 0 :(得分:9)

更新回答:

在单个文件中导入所有内容时,正确的事情无法正常工作。

挖掘事物看起来你正在遇到Babel / Jest为支持依赖全局变量的浏览器脚本(如AngularJS)所做的一些魔术。

正在发生的事情是,您的模块的angular变量与角度模拟可见的全局angular变量相同。

您可以通过在其中一个测试的顶部运行此操作来检查:

import * as angular from 'angular'
import 'angular-mocks'

console.log(angular === window.angular); // `false` in Jest!

console.log(angular.mock); // undefined
console.log(window.angular.mock); // `{...}` defined

要解决此问题,您只需在测试中使用全局angular变量。

<强>的src / __测试__ /所有功能于one.test.js

import "angular";
import "angular-mocks";

/*
Work around Jest's window/global mock magic.

Use the global version of `angular` that has been augmented by angular-mocks.
*/
var angular = window.angular;


export var app = angular.module('app', []);

app.directive('myDirective', () => ({
    link: (scope, element) => {
        console.log('This does log');
        scope.content = 'Hi!';
    },
    template: 'content: {{content}}'
}));


describe('myDirective', function(){
    var element;
    var scope;

    beforeEach(function(){
        angular.mock.module(app.name);
    });

    it('should do something', function(){
        inject(function(
            $rootScope,
            $compile
        ){
            scope = $rootScope.$new();
            element = $compile('<my-directive></my-directive>')(scope);
            scope.$digest();
        });

        expect(element.html()).toEqual('content: Hi!');
    });
});

原始回答:(这很有效,因为我在测试中意外使用了angular的全局版本。)

测试中的Angular模块未在测试中正确初始化。

您对beforeEach(app)的来电不正确。

相反,您需要使用angular.mock.module("moduleName")初始化模块。

describe('myDirective', () => {
    var element, scope

    // You need to pass the module name to `angular.mock.module()`
    beforeEach(function(){
        angular.mock.module(app.name);
    });


    // Then you can set up and run your tests as normal:
    beforeEach(inject(($rootScope, $compile) => {
        scope = $rootScope.$new()
        element = $compile('<my-directive></my-directive>')(scope)
        scope.$digest()
    }))

    it('should actually do something', () => {
        expect(element.html()).toEqual('Hi!')
    })
});

然后你的测试按预期工作:

 PASS  src\__test__\app.test.js
  myDirective
    √ should do something (46ms)

供参考,这是完整的应用程序和测试:

<强>的src /应用程序/ app.module.js

import * as angular from 'angular'

export var app = angular.module('app', []);

app.directive('myDirective', () => ({
    link: (scope, element) => {
        console.log('This does log');
        scope.content = 'Hi!';
    },
    template: 'content: {{content}}'
}))

<强>的src / __测试__ / app.test.js

import {app} from "../app/app.module";
import "angular-mocks";

describe('myDirective', function(){
    var element;
    var scope;

    beforeEach(function(){
        angular.mock.module(app.name);
    });

    beforeEach(inject(function(
        $rootScope,
        $compile
    ){
        scope = $rootScope.$new();
        element = $compile('<my-directive></my-directive>')(scope);
        scope.$digest();
    }));

    it('should do something', function(){
        expect(element.html()).toEqual('content: Hi!');
    });
});

答案 1 :(得分:0)

几年后,我遇到了同样令人困惑的行为,我想分享我发现的东西

如果您使用babel转换测试并查看导入,您将发现与以下内容类似的内容

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var angular = _interopRequireWildcard(require("angular"));
require("angular-mocks");

_interopRequireWildcard当前具有以下实现方式

function _interopRequireWildcard(obj) {
  if (obj && obj.__esModule) {
    return obj;
  } else {
    var newObj = {};

    if (obj != null) {
      for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};

          if (desc.get || desc.set) {
            Object.defineProperty(newObj, key, desc);
          } else {
            newObj[key] = obj[key];
          }
        }
      }
    }

    newObj.default = obj;
    return newObj;
  }
}

简而言之,它创建一个新对象,并从导入的对象复制所有属性。这就是angular === window.angularfalse的原因。它还说明了为什么angular.mock未定义,_interopRequireWildcard复制模块时不存在

考虑到除了可接受的答案之外,还有两种其他方法可以解决问题

使用import * as angular from 'angular'而不是使用import angular from 'angular'可以避免这种情况,因为_interopRequireDefault不会返回其他对象。 (但是,如果您使用的是TypeScript,则可能无法通过此方法正确解析“角度”的类型)

另一种选择是两次导入角度:

import 'angular'
import 'angular-mocks'
import * as angular from 'angular'