0

I have a pretty simple method that uses Spring framework and, aside from interacting with injected services, has to new an object and then get an other object using a static method, like so:

// 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);

How should I go about testing it (making sure the methods get called and the right arguments are passed into them)? I cannot simply mock the UsernamePasswordAuthenticationToken and SecurityContextHolder. I could create factories for both things, and that would solve the problem, but it feels like a huge overkill for something as simple as this. Plus then I'd have to test the factories. Is there an other way?

user5080246
  • 193
  • 1
  • 11

2 Answers2

1

The answer of @KlausGroenbaek describes module testing or acceptance testing done with the JUnit Framework. This is an important test type and definitely something you want to do.

But it is not unit testing.

UnitTests test behavior of small parts of your code in isolation, that means anything that your unit communicates with should be a mock or a data transfer object (DTO) (or otherwise "too simple to fail").


Wen I try to adopt this to your code you should refacor out the static method call SecurityContextHolder.getContext() and either get it's result (the context?) via a package private getter or inject it into your class too.

Then you can mock the three dependencies context, userDetailsService and authManager using Mockito or any other mocking framework. In case of the package private getter use Mockito.spy() together with Mockito.doReturn(cotextMock).when(myClass.getContext()) (or the equivalent of the mocking framework of your choice).
When using Mockito I prefer this form with spy objects because it does not execute the mocked method as the more common when().thenReturn() form does.

The instantiation of the UsernamePasswordAuthenticationToken can be checked indirectly by capturing the parameter of the method call authenticationManager.authenticate(token). Mockito has the ArgumentCaptor util class for this.

Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51
0

I have two answers for you, and I have done both of them before.

The approach you are using now is to test at the controller/service level without the Spring security filter chain. As you have discovered this means that you have to manager the ThreadLocal SecurityContext yourself. Normally when you want to do something before a JUnit test you put it inside a @Before annotated method.

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

and to cleanup we use @After

@After
public void test() {
    SecurityContextHolder.getContext().setAuthentication(null);
}

This approach works but it has two major disadvantages. It does not check the Spring filter chain, and it does not check Controller request routing - So I have stopped writing test that look like this, because I found a much better way that test the full web stack without using a webserver.

Welcome your new friend MockMvc! It allows you configure a Spring Test that tests the entire web layer. This allows you to check that the user is redirected back to the login, with an exception when credentials are wrong. And verify that sending invalid JSON will result in the correct 400 Bad Input.

I recommend that you start by reading the MockMvc documentation. Once you are familiar with the basics, you can apply SpringSecurity's SecurityMockMvcConfigurers.springSecurity()as described here

Community
  • 1
  • 1
Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • I apologize, but I think you misunderstood my question (maybe I wasn't specific enough). This is the code that is within my method and I need to write tests that test _this_ code (the code that's in my question). In other words, I need to write tests that make sure that this code performs proper authentication but it could actually be about anything, not necessarily authentication. The main thing here is the presense of new operator and static objects in my code. – user5080246 Jan 10 '17 at 17:23
  • I missunderstod your question, but either you have a really strange use-case, or you are using Spring security the wrong way, as all the code you listed are basically internal logic inside Spring Security. You are supposed to implement an ```AuthenticationProvider``` (and potentially a ```UserDetailsService```) which Spring will call, just like it will call ```SecurityContextHolder.getContext().setAuthentication()``` when the filter chain completes. – Klaus Groenbaek Jan 10 '17 at 17:46
  • You're right. I'm trying to manually authenticate in my controller (which received user name with password). OpenId authentication doesn't work for me, I can't use forms login because I don't have a form (it's a purely REST based thing) and I cannot use Basic auth because I don't want the browser showing enter credentials form (on auth failure). So I have to implement my own authentication. I probably should use AuthProvider but I don't understand how to use it - from what I understand going by documentation - it acts just like AuthenticationManager. Could you nudge me in the right direction? – user5080246 Jan 10 '17 at 18:05
  • When you integrate Spring with external pre-authenticated solutions (SSO), you would typically have a filter which creates a ```PreAuthenticatedAuthenticationToken```and then an ```AuthenticationProvider``` which creates a new Authentication with the GrantedAuthorities. If you get a token with every request you could take a look at how ```BasicAuthenticationFilter```is implemented. – Klaus Groenbaek Jan 10 '17 at 18:21
  • If you describe how the authentication should work I could probably create an example for you. – Klaus Groenbaek Jan 10 '17 at 18:22
  • Thanks, I'll look into these classes. What I am trying to do, is simply authenticate with my own SPA via Ajax. I want to _post_ username and password to a specific REST controller as JSON and the controller must perform authentication. I suppose, it's the same scenario as when using form authentication (_formLogin()_ in http security config) except I don't want it to redirect to login page when accessing protected controllers (since it's a REST only thing) and simply send "Not authorized" response. Do you have any ideas? – user5080246 Jan 10 '17 at 18:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/132843/discussion-between-klaus-groenbaek-and-user5080246). – Klaus Groenbaek Jan 10 '17 at 18:52