简体   繁体   English

使用 Jest 对 AngularJS 指令进行单元测试

[英]Unit testing AngularJS Directives with Jest

I feel I am missing something crucial in this extremely simplified angular directive unit test:我觉得我在这个极其简化的角度指令单元测试中遗漏了一些至关重要的东西:

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!')
    })
})

When jest runs it appears the directive has not been linked/compiled/whatever当玩笑运行时,该指令似乎尚未链接/编译/无论如何

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

    expect(received).toEqual(expected)

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

Updated answer:更新的答案:

You're right things don't work as expected when importing everything in a single file.在单个文件中导入所有内容时,您是对的,事情没有按预期工作。

Digging into things it looks like you're running into some magic that Babel/Jest does to support browser scripts that rely on globals (like AngularJS).深入研究看起来您遇到了 Babel/Jest 为支持依赖于全局变量(如 AngularJS)的浏览器脚本所做的一些魔法。

What's happening is that your module's angular variable is not the same as the global angular variable that is visible to angular-mocks.发生了什么事是你模块的angular变量是一样的全局angular变量是角嘲笑可见。

You can check this by running this at the top of one of your tests:您可以通过在其中一个测试的顶部运行它来检查这一点:

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

To work around this you just need to use the global angular variable in your tests.要解决这个问题,您只需要在测试中使用全局angular变量。

src/__test__/all-in-one.test.js : 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!');
    });
});

Original answer: (This worked because I was accidentally using the global version of angular inside my test.)原始答案:(这是有效的,因为我在测试中不小心使用了全局版本的angular 。)

The Angular module under test isn't being initialised correctly in your tests.测试中的 Angular 模块未在您的测试中正确初始化。

Your call to beforeEach(app) isn't correct.您对beforeEach(app)调用不正确。

Instead you need to use angular.mock.module("moduleName") to initialise your module.相反,您需要使用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!')
    })
});

And then your test works as expected for me:然后你的测试对我来说按预期工作:

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

For reference, here is the full app and test:作为参考,这里是完整的应用程序和测试:

src/app/app.module.js : 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 : 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!');
    });
});

I ran into the same baffling behavior a couple years later and I wanted to share what I found几年后我遇到了同样令人困惑的行为,我想分享我的发现

If you transpile the test using babel and look at the imports you will find something similar to the following如果您使用 babel 编译测试并查看导入,您会发现类似于以下内容

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

_interopRequireWildcard currently has the following implementation _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;
  }
}

In short it creates a new object and copies all the properties from the imported object.简而言之,它创建一个新对象并从导入的对象中复制所有属性。 This is why angular === window.angular is false .这就是为什么angular === window.angularfalse It also explains why angular.mock isn't defined, it didn't exist when _interopRequireWildcard made a copy of the module它还解释了为什么angular.mock没有定义,当_interopRequireWildcard复制模块时它不存在

Given that there are a couple additional ways to solve the problem in addition to the accepted answer鉴于除了公认的答案之外,还有其他几种方法可以解决问题

Instead of using import * as angular from 'angular' using import angular from 'angular' should avoid the behavior because _interopRequireDefault does not return a different object.而不是使用import * as angular from 'angular'使用import angular from 'angular'应该避免这种行为,因为_interopRequireDefault不会返回不同的对象。 (However, if you are using TypeScript it may not resolve the types for 'angular' correctly with this method) (但是,如果您使用的是 TypeScript,它可能无法使用此方法正确解析 'angular' 的类型)

Another option would be to import angular twice:另一种选择是导入 angular 两次:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM