【问题标题】:Unit testing AngularJS Directives with Jest使用 Jest 对 AngularJS 指令进行单元测试
【发布时间】:2017-08-23 22:51:42
【问题描述】:

我觉得我在这个极其简化的角度指令单元测试中遗漏了一些重要的东西:

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:
      ""

【问题讨论】:

    标签: angularjs jestjs angular-mock


    【解决方案1】:

    更新答案:

    您说得对,将所有内容导入单个文件时,事情并没有按预期工作。

    深入研究你似乎遇到了 Babel/Jest 为支持依赖全局变量(如 AngularJS)的浏览器脚本所做的一些魔法。

    发生的情况是您模块的 angular 变量与 angular-mocks 可见的全局 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/__test__/all-in-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/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/__test__/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!');
        });
    });
    

    【讨论】:

    • 非常感谢您的回复。你能告诉我你正在运行什么版本吗?当我完全复制粘贴您的代码时,我的测试仍然失败,因为 element.html() === ''。我正在使用角度:“1.6.3”,角度模拟:“1.6.3”,开玩笑:“18.1.0”。编辑:将 jest 升级到 19.0.2 和同样的问题。我与您的代码的唯一区别是合并两个文件(删除导出+导入)
    • 我已经更新了我的答案——你说得对,当一切都在一个文件中时它不起作用。
    • 谁能告诉我如何用 angular 1.6 配置 jest?我无法让它工作,看来你明白了并开始编写测试。请提供任何示例存储库链接
    • @Sly_cardinal 您能否阐明如何将被测模块的依赖项注入测试文件?我面临与@captainclam 相同的问题,并且遇到使用角度ngSanitize 依赖模块的模块的问题
    • @mtx 最好提出一个新问题,详细说明您看到的任何错误以及您需要注入/模拟的依赖项。在这里给我一个问题的链接,我很乐意看看。
    【解决方案2】:

    几年后我也遇到了同样莫名其妙的行为,我想分享我的发现

    如果您使用 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,它可能无法使用此方法正确解析 'angular' 的类型)

    另一种选择是导入 angular 两次:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-20
      • 1970-01-01
      • 2020-12-30
      • 2020-11-12
      • 2020-12-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多