如何在 Grails 单元测试中使用 Spock 模拟 passwordEncoder
Posted
技术标签:
【中文标题】如何在 Grails 单元测试中使用 Spock 模拟 passwordEncoder【英文标题】:How to mock passwordEncoder using Spock in a Grails unit test 【发布时间】:2020-07-22 12:19:50 【问题描述】:我可以使用一些建议来模拟 Grails 单元测试中使用的自动连接依赖项。我已经省略了大部分不必要的代码,只给出了测试类和被测文件类中的相关方法
class UserService
def springSecurityService // spring bean
def passwordEncoder // auto wired as per
//https://***.com/questions/33303585/spring-//security-encode-password-with- bcrypt-algorithm
.....
def passwordPreviouslyUsed(String newPassword, def userId)
def passwordExists = false
def usersPasswords = findPasswordsForUser(userId)
usersPasswords.each password ->
if (passwordEncoder.isPasswordValid(oldPassword, newPassword, null))
passwordExists = true
return passwordExists
.....
def findPasswordsForUser(def userId)
User foundUser = User.findById(userId)
def passwordsForUser = UserPasswords.createCriteria().list
eq('user', foundUser)
projections
property('password')
passwordsForUser
我的测试
class UserServiceSpec extends Specification implements DataTest, ServiceUnitTest<UserService>
def passwordEncoder
def setupSpec()
mockDomains User, UserPasswords
def setup()
def stubPasswordEncoder = Stub(passwordEncoder)
isPasswordValid(_, _, _) >> true
service.passwordEncoder = stubPasswordEncoder
void "test for user passwordPreviouslyUsed"()
given: "a user already exists"
setup()
service.createNewUser("testName", "testy@test.com", "Secret1234" )
//^(does some validation, then User.save())
User foundUser = User.findByEmail("testy@test.com")
foundUser.fullName == "testName"
long testUserId = foundUser.id
and: "we update the password for that user, and it to the userPasswords"
UserPasswords newUserPassword = new UserPasswords(
user: foundUser,
password: "newPassword1"
)
newUserPassword.save()
//use passwordPreviouslyUsed method to check a string with the same value as the
//previously
//updated password to check if it has already been used
when: "we check if the password has been used before"
def response = service.passwordPreviouslyUsed("newPassword1", fundsyId)
then:
response == true
没有存根或模拟这个依赖,我得到错误
Cannot invoke method isPasswordValid() on null object
我尝试存根密码编码器并让它返回 true
def stubPasswordEncoder = Stub(passwordEncoder)
isPasswordValid(_, _, _) >> true
service.passwordEncoder = stubPasswordEncoder
但这给出了一个错误信息:
Stub in 'spock.mock.MockingApi' cannot be applied to '(java.lang.Object, groovy.lang.Closure)'
有没有办法用 Spock 模拟这种依赖关系?
【问题讨论】:
【参考方案1】:Stub 和 Mock 接受一个类 - 你给它一个为 null 的实例 - 因此是异常。
你应该可以这样模拟它:
def mockPasswordEncoder = Mock(PasswordEncoder)
// note this is the class
// org.springframework.security.crypto.password.PasswordEncoder
【讨论】:
此外,不要从您的given:
块或测试中的其他任何地方调用 setup()
。 Spock 负责拨打setupSpec()
、setup()
、cleanup()
和cleanupSpec()
。它甚至可以确保如果基本规范和子类规范都有自己的固定方法,它们不会被覆盖,而是按定义的顺序调用。【参考方案2】:
我尝试了enrichelgeson 的方法,它奏效了! 我首先将 PasswordEncoder 导入到测试类中
import org.springframework.security.crypto.password.PasswordEncoder
然后执行正常的模拟程序。我一开始很困惑,因为他们被测类只是通过定义隐式地创建了一个类的实例。
def stubPasswordEncoder = Stub(PasswordEncoder)
isPasswordValid(_, _, _) >> true
service.passwordEncoder = stubPasswordEncoder
我还找到了另一个不需要模拟的解决方案
service.passwordEncoder = [ isPasswordValid: String rawPass, String salt, Null -> true ]
这两种方法都可以正常工作。感谢您的帮助!
【讨论】:
以上是关于如何在 Grails 单元测试中使用 Spock 模拟 passwordEncoder的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spock 控制器测试中模拟 Grails 请求对象方法