My company has been evaluating Spring MVC to determine if we should use it in one of our next projects. So far I love what I've seen, and right now I'm taking a look at the Spring Security module to determine if it's something we can/should use.
Our security requirements are pretty basic; a user just needs to be able to provide a username and password to be able to access certain parts of the site (such as to get info about their account); and there are a handful of pages on the site (FAQs, Support, etc) where an anonymous user should be given access.
In the prototype I've been creating, I have been storing a "LoginCredentials" object (which just contains username and password) in Session for an authenticated user; some of the controllers check to see if this object is in session to get a reference to the logged-in username, for example. I'm looking to replace this home-grown logic with Spring Security instead, which would have the nice benefit of removing any sort of "how do we track logged in users?" and "how do we authenticate users?" from my controller/business code.
It seems like Spring Security provides a (per-thread) "context" object to be able to access the username/principal info from anywhere in your app...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... which seems very un-Spring like as this object is a (global) singleton, in a way.
My question is this: if this is the standard way to access information about the authenticated user in Spring Security, what is the accepted way to inject an Authentication object into the SecurityContext so that it is available for my unit tests when the unit tests require an authenticated user?
Do I need to wire this up in the initialization method of each test case?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
This seems overly verbose. Is there an easier way?
The SecurityContextHolder object itself seems very un-Spring-like...
Just do it the usual way and then insert it using SecurityContextHolder.setContext() in your test class, for example:
Controller:
Authentication a = SecurityContextHolder.getContext().getAuthentication();
Test:
Authentication authentication = Mockito.mock(Authentication.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
Without answering the question about how to create and inject Authentication objects, Spring Security 4.0 provides some welcome alternatives when it comes to testing. The #WithMockUser annotation enables the developer to specify a mock user (with optional authorities, username, password and roles) in a neat way:
#Test
#WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
String message = messageService.getMessage();
...
}
There is also the option to use #WithUserDetails to emulate a UserDetails returned from the UserDetailsService, e.g.
#Test
#WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
String message = messageService.getMessage();
...
}
More details can be found in the #WithMockUser and the #WithUserDetails chapters in the Spring Security reference docs (from which the above examples were copied)
The problem is that Spring Security does not make the Authentication object available as a bean in the container, so there is no way to easily inject or autowire it out of the box.
Before we started to use Spring Security, we would create a session-scoped bean in the container to store the Principal, inject this into an "AuthenticationService" (singleton) and then inject this bean into other services that needed knowledge of the current Principal.
If you are implementing your own authentication service, you could basically do the same thing: create a session-scoped bean with a "principal" property, inject this into your authentication service, have the auth service set the property on successful auth, and then make the auth service available to other beans as you need it.
I wouldn't feel too bad about using SecurityContextHolder. though. I know that it's a static / Singleton and that Spring discourages using such things but their implementation takes care to behave appropriately depending on the environment: session-scoped in a Servlet container, thread-scoped in a JUnit test, etc. The real limiting factor of a Singleton is when it provides an implementation that is inflexible to different environments.
You are quite right to be concerned - static method calls are particularly problematic for unit testing as you cannot easily mock your dependencies. What I am going to show you is how to let the Spring IoC container do the dirty work for you, leaving you with neat, testable code. SecurityContextHolder is a framework class and while it may be ok for your low-level security code to be tied to it, you probably want to expose a neater interface to your UI components (i.e. controllers).
cliff.meyers mentioned one way around it - create your own "principal" type and inject an instance into consumers. The Spring <aop:scoped-proxy/> tag introduced in 2.x combined with a request scope bean definition, and the factory-method support may be the ticket to the most readable code.
It could work like following:
public class MyUserDetails implements UserDetails {
// this is your custom UserDetails implementation to serve as a principal
// implement the Spring methods and add your own methods as appropriate
}
public class MyUserHolder {
public static MyUserDetails getUserDetails() {
Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
return (MyUserDetails) a.getPrincipal();
}
}
}
public class MyUserAwareController {
MyUserDetails currentUser;
public void setCurrentUser(MyUserDetails currentUser) {
this.currentUser = currentUser;
}
// controller code
}
Nothing complicated so far, right? In fact you probably had to do most of this already. Next, in your bean context define a request-scoped bean to hold the principal:
<bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy/>
</bean>
<bean id="controller" class="MyUserAwareController">
<property name="currentUser" ref="userDetails"/>
<!-- other props -->
</bean>
Thanks to the magic of the aop:scoped-proxy tag, the static method getUserDetails will be called every time a new HTTP request comes in and any references to the currentUser property will be resolved correctly. Now unit testing becomes trivial:
protected void setUp() {
// existing init code
MyUserDetails user = new MyUserDetails();
// set up user as you wish
controller.setCurrentUser(user);
}
Hope this helps!
Personally I would just use Powermock along with Mockito or Easymock to mock the static SecurityContextHolder.getSecurityContext() in your unit/integration test e.g.
#RunWith(PowerMockRunner.class)
#PrepareForTest(SecurityContextHolder.class)
public class YourTestCase {
#Mock SecurityContext mockSecurityContext;
#Test
public void testMethodThatCallsStaticMethod() {
// Set mock behaviour/expectations on the mockSecurityContext
when(mockSecurityContext.getAuthentication()).thenReturn(...)
...
// Tell mockito to use Powermock to mock the SecurityContextHolder
PowerMockito.mockStatic(SecurityContextHolder.class);
// use Mockito to set up your expectation on SecurityContextHolder.getSecurityContext()
Mockito.when(SecurityContextHolder.getSecurityContext()).thenReturn(mockSecurityContext);
...
}
}
Admittedly there is quite a bit of boiler plate code here i.e. mock an Authentication object, mock a SecurityContext to return the Authentication and finally mock the SecurityContextHolder to get the SecurityContext, however its very flexible and allows you to unit test for scenarios like null Authentication objects etc. without having to change your (non test) code
Using a static in this case is the best way to write secure code.
Yes, statics are generally bad - generally, but in this case, the static is what you want. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection would be overridable with external XML. Such XML could inject the running system with a rogue principal.
I asked the same question myself over here, and just posted an answer that I recently found. Short answer is: inject a SecurityContext, and refer to SecurityContextHolder only in your Spring config to obtain the SecurityContext
General
In the meantime (since version 3.2, in the year 2013, thanks to SEC-2298) the authentication can be injected into MVC methods using the annotation #AuthenticationPrincipal:
#Controller
class Controller {
#RequestMapping("/somewhere")
public void doStuff(#AuthenticationPrincipal UserDetails myUser) {
}
}
Tests
In your unit test you can obviously call this Method directly. In integration tests using org.springframework.test.web.servlet.MockMvc you can use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user() to inject the user like this:
mockMvc.perform(get("/somewhere").with(user(myUserDetails)));
This will however just directly fill the SecurityContext. If you want to make sure that the user is loaded from a session in your test, you can use this:
mockMvc.perform(get("/somewhere").with(sessionUser(myUserDetails)));
/* ... */
private static RequestPostProcessor sessionUser(final UserDetails userDetails) {
return new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
final SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
);
request.getSession().setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext
);
return request;
}
};
}
I would take a look at Spring's abstract test classes and mock objects which are talked about here. They provide a powerful way of auto-wiring your Spring managed objects making unit and integration testing easier.
Authentication is a property of a thread in server environment in the same way as it is a property of a process in OS. Having a bean instance for accessing authentication information would be inconvenient configuration and wiring overhead without any benefit.
Regarding test authentication there are several ways how you can make your life easier. My favourite is to make a custom annotation #Authenticated and test execution listener, which manages it. Check DirtiesContextTestExecutionListener for inspiration.
After quite a lot of work I was able to reproduce the desired behavior. I had emulated the login through MockMvc. It is too heavy for most unit tests but helpful for integration tests.
Of course I am willing to see those new features in Spring Security 4.0 that will make our testing easier.
package [myPackage]
import static org.junit.Assert.*;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
#ContextConfiguration(locations={[my config file locations]})
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public static class getUserConfigurationTester{
private MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private MockHttpServletRequest request;
#Autowired
private WebApplicationContext webappContext;
#Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webappContext)
.addFilters(springSecurityFilterChain)
.build();
}
#Test
public void testTwoReads() throws Exception{
HttpSession session = mockMvc.perform(post("/j_spring_security_check")
.param("j_username", "admin_001")
.param("j_password", "secret007"))
.andDo(print())
.andExpect(status().isMovedTemporarily())
.andExpect(redirectedUrl("/index"))
.andReturn()
.getRequest()
.getSession();
request.setSession(session);
SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(securityContext);
// Your test goes here. User is logged with
}
Adding to the answer of #matsev, to write an fully functional integration test with TestRestTemplate or Webclient you can combine #SpringBootTest and #AutoconfigureMockMvc and do the following:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureMockMvc
public class UserControllerIntegrationTest {
#Autowired
private WebTestClient webTestClient;
#Test
#WithMockUser(username = "user", roles = {"USER"})
public void testUserEndpoint() {
webTestClient.get().uri("/user")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
}
Related
I have a Quarkus project where I have most of the business logic placed in services, aka injectable beans using #ApplicationScoped annotations, where all of the CRUD operations take place. In the JAX-RS resource files themselves, the bulk of the logic is just validation, often using whole validation beans. This has meant that we needed to mock our injected services when we tested the resources, to prevent the unit tests from becoming essentially integration tests. We do this having a structure like this (example project);
The file MockGreetingService.java in turn looks like this:
import io.quarkus.test.Mock;
import javax.enterprise.context.ApplicationScoped;
#Mock
#ApplicationScoped
public class MockGreetingService extends GreetingService {
#Override
public String sayHello(String name) {
return String.format("Hello %s, your id is %s", name, "1234");
}
}
Our actual project is a bit more sophisticated than this in the way that the mocks always return our DTO classes regardless of input, but the principle is the same as above. They work flawlessly for our JAX-RS resource tests. However, trying to test the actual service beans themselves means problems with this setup. I built a service test, which uses the same annotations and flow as the code below:
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
#QuarkusTest
public class GreetingServiceTest {
#Inject
GreetingService greetingService;
#Test
void checkReturnsHello () {
String result = greetingService.sayHello();
System.out.println(result);
Assertions.assertEquals("hello Martin! Your country is Italy", result);
}
}
With the dependency injection in the class above, which we don't do in our resource tests, I expected Quarkus to understand that we want to use the original service in this test. How foolish of me. A simple log has shown that the mock service methods indeed still run in the latter test above.
Now I wonder - is it a way to disable the mock for this latter test? Preferably without having to modify or remove the mock classes, although I realize that might not be possible in the way I imagine it to be. Thanks in advance!
Sounds like a use case for qualifiers, which enable you to have different implementation beans, and to choose at the injection point which type of bean you prefer:
https://jakarta.ee/specifications/cdi/2.0/cdi-spec-2.0.html#qualifiers
As an alternative, you may also decide to instantiate your service on your own, not using cdi in any way.
I want to unit test my controllers security using the method based security when I've checked the docs I've found this but it uses #RunWith and it doesn't seem to be loading only the security config.
What I tried to do:
Loading my secuirty config
#ContextConfiguration(classes = {SecurityConfigImpl.class})
public class PeopleGQLApiSecutiryTest {
private final PersonService personService = mock(PersonService.class);
private final PeopleGQLApi peopleGQLApi = new PeopleGQLApi(personService);
#Test
#WithAnonymousUser
void itShouldCreateNewPerson() {
when(personService.createNewPerson(any(Person.class))).thenReturn(new Person());
// this should fail!
peopleGQLApi.createPerson(
Person.LevelEnum.WEAK,
// rest of the arguments
);
}
}
This one should fail (by throwing an expection or anything like that) because create person is a secured function with ROLE_ADMIN.
Note: this is a GraphQL mutation
#MutationMapping("createPerson")
#Secured({"ROLE_ADMIN"})
public Person createPerson(
#Argument("level") Level level,
// rest of the arguments
) {
// method implementation
}
I can't use a fully fledged #SpringBootTest because I will have to provide mongodb config too which out of the scope of this test.
I think the word you are looking for is #WithMockUser
You can use something like #WithMockUser(authorities = "ROLE_ADMIN") to mock a user with this role
Also for more tweaks, you can use the Spring Security Context
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("username", "password"));
SecurityContextHolder.setContext(securityContext);
And you can add any authorities to your security context or read them.
better to add them to securityUtil class to be used in multiple situations as well.
Please make sure that you have an assertion to validate the unauthorized requests inside your test case
I added #preAuthorize("#securityService.hasPermission()") on top of my controller method and created related bean with given method-
#Configuration
public class SecurityConfig {
#Bean
public PreAuthorizationSecurityService securityService() {
return new PreAuthorizationSecurityService();
}
}
Now, I'm using MockMvc for testing my controller. When I add SecurityConfig.class to ContextConfiguration of the test class,controller method checks for permission. But, I want to mock PreAuthorizationSecurityService in a way that my class doesn't have to import SecurityConfig.class for testing purposes.
If you just want to declare a mock of your service instead of importing the entire SecurityConfig, you can easily do so by declaring this in your test config :
#Configuration
public class TestConfig {
#Bean
public PreAuthorizationSecurityService mockedSecurityService() {
//providing you use Mockito for mocking purpose
return mock(PreAuthorizationSecurityService.class);
}
}
And then set your mock to return true when needed. You can also provide your own mocked implementation that always return true.
That said, this is not the usual way to use Spring Security. And you should consider refactoring your app to use the standard role-based system, it will save you some trouble.
I don't know what your PreAuthorizationSecurityService looks like and if this can apply in your situation but in most cases, it should and that's what you should aim for.
With this standard role based approach, Spring Security Test (v4+ I think) easily allows you to mock connected user with given roles with annotation like #WithMockUser.
I am using Spring Boot 1.3, Spring 4.2 and Spring Security 4.0. I am running integration tests using MockMvc, for example:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
In my tests I am simulating a user login like this:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
This works fine for my methods that are annotated with #PreAuthorize, for example when calling a method like this from a test:
#PreAuthorize("#permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
the principle, my CurrentUser object, is non-null in PermissionsService#canDoThisThing.
I have a class annotated with #ControllerAdvice that adds the currently logged-in user to the model so it can be accessed in every view:
#ControllerAdvice
public class CurrentUserControllerAdvice {
#ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
This works fine when running the application, however (and this is my problem) - when running my tests the authentication parameter passed in to the getCurrentUser method above is always null. This means any references to the currentUser attribute in my view templates cause errors, so those tests fail.
I know I could get round this by retrieving the principle like this:
authentication = SecurityContextHolder.getContext().getAuthentication();
but I would rather not change my main code just so the tests work.
Setting the SecurityContextHolder does not work when using Spring Security with MockMvc. The reason is that Spring Security's SecurityContextPersistenceFilter attempts to resolve the SecurityContext from the HttpServletRequest. By default this is done using HttpSessionSecurityContextRepository by retrieving at the HttpSession attribute named SPRING_SECURITY_CONTEXT. The attribute name is defined by the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY. Whatever SecurityContext is found in the HttpSession will then be set on the SecurityContextHolder which overrides the value you previously set.
Manually Solving the Issue
The fix that involves the least amount of change is to set the SecurityContext in the HttpSession. You can do this using something like this:
MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
The key is to ensure that you set the HttpSession attribute named SPRING_SECURITY_CONTEXT to the SecurityContext. In our example, we leverage the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY to define the attribute name.
Spring Security Test
Spring Security 4.0 has officially added test support. This is by far the easiest and most flexible way to test your application with Spring Security.
Add spring-security-test
Since you are using Spring Boot, the easiest way to ensure you have this dependency is to include spring-security-test in your Maven pom. Spring Boot manages the version, so there is no need to specify a version.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Naturally, Gradle inclusion would be very similar.
Setting up MockMvc
In order to integrate with MockMvc there are some steps you must perform outlined in the reference.
The first step is to ensure you use #RunWith(SpringJUnit4ClassRunner.class). This should come as no surprise since this is a standard step when testing with Spring applications.
The next step is to ensure you build your MockMvc instance using SecurityMockMvcConfigurers.springSecurity(). For example:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class MyTests {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
Running as a User
Now you can easily run with a specific user. There are two ways of accomplishing this with MockMvc.
Using a RequestPostProcessor
The first option is using a RequestPostProcessor. For your example, you could do something like this:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
Using Annotations
You can also use annotations to specify the user. Since you use a custom user object (i.e. CurrentUser), you would probably consider using #WithUserDetails or #WithSecurityContext.
#WithUserDetails makes sense if you expose the UserDetailsService as a bean and you are alright with the user being looked up (i.e. it must exist). An example of #WithUserDetails might look like:
#Test
#WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
}
The alternative is to use #WithSecurityContext. This makes sense if you do not want to require the user to actually exist (as is necessary for WithUserDetails). I won't elaborate on this as it is well documented and without more details about your object model, I cannot provide a concrete example of this.
In my project I'm having trouble doing unit testing. One issue is that just doing an integration test is much faster to write and also tests that the components actually work together. Unit testing novel "algorithms" or so seems much easier. Unit Testing service classes it just feels wrong and useless.
I'm using mockito to mock spring data repository (and hence DB access). The thing is if i tell the mocked repository to return entity A on method call getById it will obviously return that and the service will return it too. Yes, the service does some extra stuff, but very minor things, like load lazy collections (from hibernate). Obviously I don't have any lazy collections (proxies) in a unit test.
Example:
#Test
public void testGetById() {
System.out.println("getById");
TestCompound expResult = new TestCompound(id, "Test Compound", "9999-99-9", null, null, null);
TestCompoundRepository mockedRepository = mock(TestCompoundRepository.class);
when(mockedRepository.findOne(id)).thenReturn(expResult);
ReflectionTestUtils.setField(testCompoundService, "testCompoundRepository",
mockedRepository, TestCompoundRepository.class);
TestCompound result = testCompoundService.getById(id);
assertEquals(expResult, result);
}
hooray, the rest succeeds. What a surprise! Not really no.
Can some one explain to me what I'm doing wrong? Or else what the point of such a test is? I mean I tell to return expResult and then it is returned. Wow. What a surprise! Feels like I'm testing if mockito works and not my Service.
EDIT:
The only benefit I see if some were stupid error happens like leaving an unwanted line there that sets return value to null or something similar stupid. Such cases would be caught by the unit test. Still the "reward-effort" ratio seems bad?
Question might be a bit old but I will put an answer in case someone stumbles across.
I'm using Mockito and JUnit.
AccountRepository is a plain spring data repository extending JPARepository.
Account is a plain JPA entity.
To test your services and mock Spring Data repositories, you need something like below.
package foo.bar.service.impl;
import foo.bar.data.entity.Account;
import foo.bar.data.repository.AccountRepository;
import foo.bar.service.AccountService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {
#Mock
private static AccountRepository accountRepository;
#InjectMocks
private static AccountService accountService = new AccountServiceImpl();
private Account account;
#Test
public void testFindAccount() {
Integer accountId = new Integer(1);
account = new Account();
account.setId(accountId);
account.setName("Account name");
account.setCode("Accont code");
account.setDescription("Account description");
Mockito.when(accountRepository.findOne(accountId)).thenReturn(account);
Account retrivedAccount = accountService.findAccount(accountId);
Assert.assertEquals(account, retrivedAccount);
}
}
One of the reasons I like testing my Spring Data repositories is to test that I have defined my JPA mappings correctly. I do not use a mocking framework for these tests, I use the Spring Test framework which actually bootstraps the container allowing me to autowire the actual repository into the Junit test so that I may execute tests against it.
I agree with your thoughts that mocking the repository is pretty useless. Since your using Spring I would suggest leveraging the Spring Test framework to perform real tests against your repositories, which can be executed against an embedded database such as H2 in a more unit test based fashion or your actual database implementation such as Oracle or MySql, to conduct more of an integration test. (Execute these against a copy of a development database) These tests will reveal fallacies in your JPA mappings and other items such as improper cascades setup in the database.
Here is an example of one of my tests on GitHub. Notice how the framework actually autowires the repository into the test. The repository also contains an example of how to configure the Spring Test framework, which I have also demonstrated in this blog post.
In conclusion, I do not believe you will receive any of the benefits of testing a repository that I have discussed from using a mock of the repository.
One additional note I wanted to add, is that mocks are not really intended for use in the actual class under test. Their use is for providing required dependencies to a class under test.
You can use this library: https://github.com/agileapes/spring-data-mock
This will mock your repository for you, while allowing you to implement custom functionality for any method as well as your native query methods.
You exactly right. It is clear unit test. And it will never fail (so, it is useless) I think you need at integration test to test real JPA repository with real database (H2 in memory for example) (as I always do).
And it is better to test your services (theirs interfaces). If after some time you will change your storage (to Mongo for example) - you will be able to use your service tests to ensure all works as before.
After some time you will be suprised how many DB\JPA-related problems (constraints, optimistic locks, lazy-loading, duplicate id, some hibernate issues and so on) you find.
Also, try to develop via tests - not just write test after implementation. Instead before creation of new method in service - create test for it, implement service method and only after just recheck it in real application. At least it is much faster to start test than a server.
So, do not create tests to have a lot of them. Find how they may help you.
Usage of mocks for repositories is not good idea. Test how your services work together with Hibernate\JPA\Database. Most part of problems is located beetwen layers.
You can mock the repository and inject it to the service, this is the way; but, if you just instantiate the service with #Mock of repositories, it would be better, if you define the repositories as private final fields in the service and use a constructor of all repositories. That way, if you add another repository to the service, the test will fail and you have to change it, which is the purpose.
Imagine this service:
class OrderService {
private final UserRepository userRepos;
public OrderService(UserRepository userRepos) {
this.userRepos = userRepos;
}
...
}
And this test:
class OrderServiceTests {
#Mock
private UserRepository userRepos;
private OrderService service;
private OrderServiceTests() {
this.service = new OrderService(this.userRepos);
}
}
Now, if we add another dependency to the service:
class OrderService {
private final UserRepository userRepos;
private final AddressRepository addRepos;
public OrderService(UserRepository userRepos, AddressRepository addRepos) {
this.userRepos = userRepos;
this.addRepos = addRepos;
...
}
The previous test will fail because the constructor has changed. If you use #InjectMocks this will not happen; the injection happens behind the curtain and we are not clear what happens; this may not be desirable.
Another thing is, I don't agree that integration test will cover all the cases that unit tests will cover; it may but not always the case. Even the controller can be unit-tested with mocks; after all the tests are meant to cover all the code we have written, so they must be fine-grained; imagine when we follow TTD and we only complete the controller and services level: how we proceed without controller unit testing?
Assuming that we have the below Service class
#Service
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
private EmployeeRepository employeeRepository;
#Override
public Employee getEmployeeByName(String name) {
return employeeRepository.findByName(name);
}
}
Test class:
#RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {
#TestConfiguration
static class EmployeeServiceImplTestContextConfiguration {
#Bean
public EmployeeService employeeService() {
return new EmployeeServiceImpl();
}
}
#Autowired
private EmployeeService employeeService;
#MockBean
private EmployeeRepository employeeRepository;
// write test cases here
}
To check the Service class, we need to have an instance of Service class created and available as a #Bean so that we can #Autowire it in our test class. This configuration is achieved by using the #TestConfiguration annotation.
During component scanning, we might find components or configurations created only for specific tests accidentally get picked up everywhere. To help prevent that, Spring Boot provides #TestConfiguration annotation that can be used on classes in src/test/java to indicate that they should not be picked up by scanning.
Another interesting thing here is the use of #MockBean. It creates a Mock for the EmployeeRepository which can be used to bypass the call to the actual EmployeeRepository:
#Before
public void setUp() {
Employee alex = new Employee("alex");
Mockito.when(employeeRepository.findByName(alex.getName()))
.thenReturn(alex);
}
After the setup, we can easily test our service like:
#Test
public void whenValidName_thenEmployeeShouldBeFound() {
String name = "alex";
Employee found = employeeService.getEmployeeByName(name);
assertThat(found.getName())isEqualTo(name);
}
For more in depth knowledge check:
https://www.baeldung.com/spring-boot-testing