对启用 Keycloak 的 Angular 应用程序进行单元测试

Unit Testing a Keycloak-enabled Angular app

我正在尝试为使用 Keycloak 进行身份验证的 Angular 应用编写测试套件。

但是,由于 Keycloak 需要您手动 bootstrap Angular 并设置一些拦截器,由于以下错误,我无法启动任何测试:

Error: $injector:unpr
Unknown Provider: AuthProvider <- Auth <- authInterceptor <- $http <- $templateRequest <- $route

这是引发错误的拦截器代码:

angular.module('MPMReportGenerator')
  .factory('authInterceptor', function authInterceptor ($q, Auth, $log) {
    return {
      request: function (config) {
        var deferred = $q.defer()
        Auth.updateToken(5).success(function () {
          config.headers = config.headers || {}
          config.headers.Authorization = 'Bearer ' + Auth.token
          deferred.resolve(config)
        }).error(function () {
          deferred.reject('Failed to refresh token')
        })
        $log.info(deferred.promise)
        return deferred.promise
      }
    }
  })

我的想法是我应该模拟拦截器并让它 return 请求。 但是,我看不出我该怎么做,因为这个拦截器从来没有作为依赖注入到任何地方,它只是用上面的块声明,就是这样。我对模拟服务的理解是它们需要被注入到某个地方才能被模拟。

我在 Angular 中对 Keycloak 的实现直接来自他们的例子,如果有帮助的话。

编辑

我一直在尝试将模拟的 Auth 模块注入到我正在为其编写测试的服务中,但仍然没有任何变化。
总的来说,我对单元测试还很陌生,所以我有点迷失了试图追踪这一点。我觉得我知道问题出在哪里,但不知道如何解决(在应用程序 bootstrap 期间添加了 Auth 服务,我需要模拟它才能正常工作,但似乎我没有知道 how/where 正确地模拟它)

完整的测试代码如下:

describe('Services', function () {
  'use strict'

  beforeEach(module('MPMReportGenerator'))

  module(function ($provide) {
    $provide.factory('Auth', function () {
      return null
    })
  })

  var sectionService, $httpBackend, mockAuth
  beforeEach(inject(function (_sectionService_, _$httpBackend_, Auth) {
    sectionService = _sectionService_
    $httpBackend = _$httpBackend_
    mockAuth = Auth
  }))

  it('should get sections', function () {
    $httpBackend.expect('GET', '/MPMReportGenerator/api/categories/all').respond(200)

    sectionService.getSections()

    expect($httpBackend.flush).not.toThrow()
  })
})

编辑 2

我已经通过制作 Auth 的模拟版本设法克服了最初的错误。
我现在面临着实现 Keycloak 的 Javascript 库的模拟版本的问题。

我目前的模拟代码如下:

beforeEach(module(function ($provide) {
    $provide.factory('Auth', function ($q) {
      return {
        updateToken: function (minValidity) {
          return {
            success: function (fn) {
              var deferred = $q.defer()
              deferred.resolve('')
              fn(deferred.promise)
            },
            error: function (fn) {
              var deferred = $q.defer()
              deferred.resolve('Error')
              fn(deferred.promise)
            }
          }
        },
        token: 'thisisafaketokenfortesting'
      }
    })
  }))

并抛出此错误:

Expected function not to throw, but it threw TypeError: undefined is not an object (near '...}).error(function () {...').
    target/MPMReportGenerator-1.0.0/js/app.service.spec.js:42:43
    loaded@http://localhost:9876/context.js:151:17

我的实际测试是这样的:

  it('should get sections', function () {
    $httpBackend.expect('GET', '/MPMReportGenerator/api/categories/all').respond(200)
    sectionService.getSections()
    expect($httpBackend.flush).not.toThrow()
  })

终于想通了

如果有人想用 keycloak 测试 Angular 应用程序,这里是所需的代码:

beforeEach(
  module(function ($provide) {
    $provide.factory('Auth', function ($q) {
      return {
        updateToken: function (minValidity) {
          return {
            success: function () {
              return {
                error: function () {
                  var deferred = $q.defer()
                  return deferred.promise
                }
              }
            }
          }
        },
      token: 'thisisafaketokenfortesting'
    }
  })
}))

请注意,如果您打算测试官方示例中提供的拦截器,您可能需要模拟 keycloak 库的其他部分。

编辑

不要使用上面的代码,下面的代码效果更好:

$provide.factory('Auth', function () {
  return {
    updateToken: function (minValidity) {
      return {
        success: function () {
          return this
        },
        error: function () {
          return this
        }
      }
    },
    token: 'thisisafaketokenfortesting'
  }
})