如何对使用springSecurityService的控制器进行单元化?

时间:2011-12-15 07:58:44

标签: unit-testing grails mocking spring-security

我有一个类似的用户类:

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

非常感谢任何建议。

4 个答案:

答案 0 :(得分:8)

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

  1. 上述答案可行,但正如我所说,我个人不喜欢
  2. 不要进行单元测试。将所有遇到这种情况的测试写成集成测试。
  3. 用虚假服务嘲笑它。
  4. 如果这个代码(或遇到相同问题的代码)散布在整个应用程序中,那么你可能想要找出一种方法来模拟所有测试用例的单元测试中的这些调用,这样你就不会在任何地方重复您的设置工作。模拟这个的简单方法是使用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,因为如果你实例化一个实际的服务,那么你最终可能会意外地在不知不觉中调用该服务上的实际方法,并且你的测试通过了错误的原因。我宁愿得到一个没有这样的方法错误而不是通过不应该通过的测试。

答案 1 :(得分:0)

我这样做了:

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

答案 2 :(得分:0)

我认为正确的做法是嘲笑服务。您将要测试可以作为返回值的不同情况,并将正确的值传递给服务方法。

@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
}

答案 3 :(得分:-1)

在我的情况下,我试图覆盖SecUser的encodePassword()实现 - 它调用springSecurityService.encodePassword()。

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

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

我为什么需要这个?