0

I'm trying to test some controller with method-level spring security and I want to mock out the repository dependencies in the controller. Basically I want to test that (a) the methods are enforcing security and (b) other beans invoked in SpEL expressions are working.

My issue is that when using Mockito's @InjectMocks for instantiating the controller the spring security proxies are not being applied to the controller and the method security is bypassed. If I use @Autowired to allow Spring to create the controller, my custom method level security logic does get called but the @Mock objects are not injected.

@RestController
@RequestMapping("/api/projects/{projectId}")
public class ProjectKeywordResource {

    //I want to mock this repository
    @Inject
    private ProjectKeywordRepository projectKeywordRepository;

    //Invokes another bean if user not assigned admin role.
    @PreAuthorize("hasRole('ROLE_ADMIN')" + " or "
            + "@projectService.canEditProjectData(#projectId)")
    @RequestMapping(value = "/projectKeywords", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<ProjectKeyword> create(
            @PathVariable String projectId,
            @RequestBody ProjectKeyword projectKeyword)
        throws URISyntaxException {

        projectKeywordRepository.save(projectKeyword);
        return ResponseEntity.created(
            new URI("/api/" + projectId + "projectKeywords/"
                    + projectKeyword.getId())).body(result);
    }
}

My Test Case is here:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class ProjectKeywordResourceSecurityTest {

    private static final String DEFAULT_PROJECT_ID = "1";

    @Mock
    private ProjectKeywordRepository projectKeywordRepository;

    //@Inject  - Adding the following annotation adds the necessary spring security proxies,
    but then ProjectKeywordResource uses the real ProjectKeywordRepository not the mock one.
    @InjectMocks
    private ProjectKeywordResource projectKeywordResource;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);      
    }

    @Test(expected = AccessDeniedException.class)
    @WithMockUser
    @Transactional
    public void testCreateThrowsAccessDenied() throws Exception {

        projectKeywordResource.create(DEFAULT_PROJECT_ID, createDefaultProjectKeyword());
    }

    @Test
    @WithMockUser(username = "admin", roles={"ADMIN"})
    @Transactional
    public void testCreateAuthorizationSuceedsForAdminUser() throws Exception {

        projectKeywordResource.create(DEFAULT_PROJECT_ID, createDefaultProjectKeyword());
    }
}

Is there a bit of config magic that allows me to wrap the Mockito mock controller with the necessary spring proxies, or alternatively force the use of the Mock on the injected bean in my test case?

Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
Grant Lay
  • 800
  • 7
  • 17
  • You need the Spring context to run the tests the way you intend them to. Take a look at the answers to http://stackoverflow.com/questions/2457239/injecting-mockito-mocks-into-a-spring-bean and http://stackoverflow.com/questions/19808326/how-to-inject-a-mock-in-a-spring-context for help on injecting mock beans into Spring. – Bewusstsein Nov 23 '15 at 05:43

1 Answers1

1

The link that Bewusstsein posted got me on the right track to a viable answer posted by jfcorugedo. Basically what I had to do was to create a new bean in my test configuration class that mocks the Repository class and annotate it with the @Primary annotation. Adding the Spring profile annotation allows these beans to be switched off by default and therefore doesn't interfere with other tests. The revised test class is:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@ActiveProfiles({"useMockRepositories","default"})
public class ProjectKeywordResourceSecurityTest {

private static final String DEFAULT_PROJECT_ID = "1";

@Inject
private ProjectKeywordResource projectKeywordResource;

@Test(expected = AccessDeniedException.class)
@WithMockUser
public void testCreateThrowsAccessDenied() throws Exception {

    projectKeywordResource.create(DEFAULT_PROJECT_ID,   createDefaultProjectKeyword());
}

@Test
@WithMockUser(username = "admin", roles={"ADMIN"})
public void testCreateAuthorizationSuceedsForAdminUser() throws Exception {

    projectKeywordResource.create(DEFAULT_PROJECT_ID, createDefaultProjectKeyword());
}

My Test Configuration class has the following:

@Configuration
public class TestConfiguration {

@Profile("useMockRepositories")
@Bean
@Primary
public ProjectKeywordRepository MockProjectKeywordRepository() {
    return Mockito.mock(ProjectKeywordRepository.class);
    }
}
Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
Grant Lay
  • 800
  • 7
  • 17