Java 新对象的测试方法,并使用静态方法

Java 新对象的测试方法,并使用静态方法,java,spring,unit-testing,Java,Spring,Unit Testing,我有一个非常简单的方法,它使用Spring框架,除了与注入的服务交互之外,还必须新建一个对象,然后使用静态方法获取另一个对象,如下所示: // These two guys are injected: UserDetailsService userDetailsService; AuthenticationManager authManager; UserDetails userDetails = userDetailsService.loadUserByUsername(name); Au

我有一个非常简单的方法,它使用Spring框架,除了与注入的服务交互之外,还必须新建一个对象,然后使用静态方法获取另一个对象,如下所示:

// These two guys are injected:
UserDetailsService userDetailsService;
AuthenticationManager authManager;

UserDetails userDetails = userDetailsService.loadUserByUsername(name);

Authentication token = new UsernamePasswordAuthenticationToken(userDetails,
        password, userDetails.getAuthorities());

Authentication authentication = authenticationManager.authenticate(token);

SecurityContextHolder.getContext().setAuthentication(authentication);

我应该如何进行测试(确保调用了这些方法并向它们传递了正确的参数)?我不能简单地模仿用户名密码身份验证令牌SecurityContextHolder。我可以为这两件事创建工厂,这将解决问题,但对于像这样简单的事情来说,这感觉像是一个巨大的杀伤力。另外,我还得对工厂进行测试。还有别的办法吗?

我有两个答案给你,我以前都做过

您现在使用的方法是在控制器/服务级别进行测试,而不使用Spring security筛选器链。正如您所发现的,这意味着您必须自己管理ThreadLocal SecurityContext。 通常,当您想在JUnit测试之前做一些事情时,可以将它放在@before注释的方法中

@Before
public void before() {
    UserDetails userDetails = userDetailsService.loadUserByUsername(name);

    Authentication token = new UsernamePasswordAuthenticationToken(userDetails,
            password, userDetails.getAuthorities());

    Authentication authentication = authenticationManager.authenticate(token);

    SecurityContextHolder.getContext().setAuthentication(authentication);
}
为了清理,我们使用@After

@After
public void test() {
    SecurityContextHolder.getContext().setAuthentication(null);
}
这种方法可行,但有两个主要缺点。它不检查Spring过滤器链,也不检查控制器请求路由——因此我停止编写类似这样的测试,因为我找到了一种更好的方法,可以在不使用web服务器的情况下测试整个web堆栈

欢迎您的新朋友MockMvc!它允许您配置测试整个web层的Spring测试。这允许您检查用户是否重定向回登录,但凭据错误时除外。并验证发送无效JSON将导致正确的错误输入


我建议您从阅读文档开始。一旦您熟悉了基本知识,就可以应用SpringSecurity的
SecurityMockMvcConfigurers.SpringSecurity()
,@KlausGroenbaek的答案描述了使用JUnit框架完成的模块测试或验收测试。这是一种重要的测试类型,也是您想要做的事情

但这不是单元测试

UnitTests单独测试代码小部分的行为,这意味着您的单元与之通信的任何东西都应该是模拟对象或数据传输对象(DTO)(或者“太简单而不能失败”)


如果我尝试在代码中采用这种方法,那么您应该重构静态方法调用
SecurityContextHolder.getContext()
,或者通过包私有getter获取它的结果(上下文?),或者将其注入到类中

然后,您可以使用Mockito或任何其他模拟框架模拟三个依赖项
context
userdetailssservice
authManager
。对于包private getter,请使用
Mockito.spy()
Mockito.doReturn(cotextMock).when(myClass.getContext())
(或您选择的模拟框架的等效版本)。
当使用Mockito时,我更喜欢使用spy对象的这个表单,因为它不会像更常见的
When()那样执行mocked方法


通过捕获方法调用
authenticationManager.authenticate(token)
的参数,可以间接检查
UsernamePasswordAuthenticationToken
的实例化。Mockito为此提供了
ArgumentCaptor
util类。

很抱歉,但我想你误解了我的问题(可能我不够具体)。这是我的方法中的代码,我需要编写测试这些代码的测试(我的问题中的代码)。换句话说,我需要编写测试来确保这段代码执行正确的身份验证,但它实际上可能涉及任何内容,不一定是身份验证。这里最主要的是在我的代码中出现了新的操作符和静态对象。我误解了你的问题,但要么你有一个非常奇怪的用例,要么你用错误的方式使用Spring security,因为你列出的所有代码基本上都是Spring security内部的逻辑。您应该实现一个
AuthenticationProvider
(可能还有一个
UserDetailsService
),Spring将调用该服务,就像它在过滤器链完成时调用
SecurityContextHolder.getContext().setAuthentication()
一样。您是对的。我正在尝试在我的控制器中手动进行身份验证(该控制器接收到带有密码的用户名)。OpenId身份验证对我不起作用,我不能使用forms登录,因为我没有表单(这是一个纯粹基于REST的东西),我不能使用基本身份验证,因为我不希望浏览器显示enter凭据表单(在身份验证失败时)。因此,我必须实现自己的身份验证。我可能应该使用AuthProvider,但我不知道如何使用它——从我对文档的理解来看——它的作用就像AuthenticationManager。你能告诉我正确的方向吗?当你将Spring与外部预认证解决方案(SSO)集成时,通常会有一个过滤器,它创建一个
预认证的身份验证令牌
,然后是一个
身份验证提供者
,它创建一个与授权机构的新身份验证。如果每个请求都有一个令牌,那么您可以看看
BasicAuthenticationFilter
是如何实现的。如果您描述身份验证应该如何工作,我可能会为您创建一个示例。