【问题标题】:How to unit test controllers that use springSecurityService?如何对使用 springSecurityService 的控制器进行单元测试?
【发布时间】:2012-01-20 22:57:57
【问题描述】:

我有一个这样的用户类:

class User {
    transient springSecurityService
    String displayName
    String password
<snip>
    protected void encodePassword() {
        password = springSecurityService.encodePassword(password)
    }
}

还有一个UserController。 我正在尝试为UserController 编写单元测试,但是在保存、更新和删除测试时出现此错误:

java.lang.NullPointerException: Cannot invoke method encodePassword() on null object

我必须在模拟方式中设置什么才能使其正常工作?

我尝试了多种模拟代码的组合,例如以下,但我不知所措。

defineBeans {
    springSecurityService(SpringSecurityService)
}

任何建议将不胜感激。

【问题讨论】:

  • 我遇到了完全相同的问题。据我所知,根据文档,defineBeans() 应该可以工作。不幸的是,它没有效果。

标签: unit-testing grails mocking spring-security


【解决方案1】:

我个人不喜欢在生产代码中添加逻辑来帮助满足测试。有时你必须决定什么是最好的。几个选项...

  1. 上述答案可行,但正如我所说,我个人不喜欢
  2. 不要进行单元测试。将遇到这种情况的所有测试编写为集成测试。
  3. 使用虚假服务进行模拟。

如果此代码(或遇到相同问题的代码)遍布整个应用程序,那么您可能想找到一种方法在所有测试用例的单元测试中模拟这些调用,这样您就不会到处重复你的设置工作。一个简单的模拟方法是使用 metaClassing。

@Test
public void something() {
   def user = ...
   def springSecurityService = new Object()
   springSecurityService.metaClass.encodePassword = {String password -> "ENCODED_PASSWORD"}
   user.springSecurityService = springSecurityService
   ...
}

现在当springSecurityService.encodePassword 被调用时,它应该返回“ENCODED_PASSWORD”。我还创建了一个Object,而不是实例化一个新的SpringSecurityService,因为如果您实例化一个实际的服务,那么您最终可能会在不知不觉中意外地调用该服务上的实际方法,并且由于错误的原因让您的测试通过。我宁愿得到一个没有这样的方法错误,也不愿得到一个不应该通过的通过测试。

【讨论】:

  • 好答案!顺便说一句,我将“service.metaClass.encodePassword =”行更正为“springSecurityService.metaClass.encodePassword =”
【解决方案2】:

我是这样做的:

    protected void encodePassword() {
        // SpringSecutiryService is not injected in tests.
        if (springSecurityService)
            password = springSecurityService.encodePassword(formPassword)
    }

【讨论】:

  • 这不是一个理想的解决方案,看起来更像是一个让测试通过的修复程序。如果安全服务在生产中由于某种奇怪的原因被取消怎么办......然后你的所有密码都不会被散列(没有错误消息)
【解决方案3】:

我认为正确的做法是模拟服务。您需要测试可能是返回值的不同情况,以及是否将正确的值传递给服务方法。

@Test
public void something() {
   def user = ...
   def expectedPassword = 'mock encoded pass' 
   controller.springSecurityService = [encodePassword: { String passwd -> return expectedPassword }]

   ...
}

@Test
public void something() {
   def user = ...
   def expectedPassword = 'mock encoded pass' 
   def mockSecurityService = mockFor(SpringSecurityService)
   mockSecurityService.demand.encodePassword { String passwd -> return expectedPassword}
   controller.springSecurityService = mockSecurityService.createMock()

   ...
   mockSecurityService.verify() // throws exception if demands aren't met
}

【讨论】:

    【解决方案4】:

    在我的例子中,我试图覆盖 SecUser 的 encodePassword() 实现 - 它调用 springSecurityService.encodePassword()。

    我很惊讶,因为我需要覆盖类和实例(如果我没有覆盖任何东西,它会失败):

    SecUser.metaClass.encodePassword = { 'a' }
    user.metaClass.encodePassword = { 'b' }
    

    知道我为什么需要这个吗?

    【讨论】:

      猜你喜欢
      • 2012-04-24
      • 2018-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-26
      • 2012-07-08
      • 2013-03-09
      • 1970-01-01
      相关资源
      最近更新 更多