Search code examples
javaspringspring-bootspring-securityintegration-testing

NoClassDefFoundError - org/springframework/security/oauth2/client/registration/ClientRegistration when testing


I wrote a simple spring boot application which uses OAuth2 using Firebase.

Here's the configuration

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
        security
                .cors()
                .and()
                .csrf().disable()
                .authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();

        return security.build();
    }

}

I have a controller which I want to test using MockMvc

Here's the test file

@WebMvcTest(CodeController.class)
@WebAppConfiguration
@ContextConfiguration(classes = SecurityConfig.class)
public class CodeControllerTests {

    @MockBean
    private CodeExecutionService codeExecutionService;
    @MockBean
    private ProblemService problemService;

//    @MockBean
//    private ProblemRepo problemRepo;

    @MockBean
    private TestCaseValidationService validationService;
//    @MockBean
//    private ProblemRepo problemRepo;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }


    @Test

    void runTestCode() throws Exception {

        RunCodeDTO runCodeDTO = new RunCodeDTO("python", "something", "two-sum");
        Problem problem = ProblemUtils.getTwoSum();
        UserCode userCode = new UserCode(runCodeDTO.code(), runCodeDTO.language());
        userCode.mergeWithStub(problem.getCodeRunStub());

        List<TestResult> testResults = problem.getRunTestCases()
                .stream()
                .map(testCase -> new TestResult(testCase, Status.SUCCESS, ""))
                .toList();

        List<TestOutput> testOutputs = testResults
                .stream()
                .map(result -> new TestOutput(result.testCase(), new ValidationResult(Status.SUCCESS, "Test Case Passed")))
                .toList();


        when(problemService.getProblem(runCodeDTO.problemId())).thenReturn(Optional.of(problem));
        when(codeExecutionService.executeAllTestCases(problem.getRunTestCases(), userCode)).thenReturn(testResults);
        when(validationService.validateAllTestResults(testResults, problem.getOutputType(), problem.getValidationType())).thenReturn(testOutputs);

        mockMvc
                .perform(
                    MockMvcRequestBuilders.post("/code/test")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("")
                            .with(SecurityMockMvcRequestPostProcessors.oauth2Login())
                )
                .andExpect(status().isOk());

    }

}

I'm trying to mock authorization using the SecurityMockMvcRequestPostProcessors.oauth2Login() but I get a NoClassDefFoundError - org/springframework/security/oauth2/client/registration/ClientRegistration.

However, the actual application works without any issue. It's only the test where I get this error


Solution

  • @m-denium is right in his comment, plus you are trying to build a client Authentication implementation (oauth2Login() instantiates an OAuth2AuthenticationToken) when testing a resource-server (with resource-server dependencies, reason for classes under org/springframework/security/oauth2/client/ not being found).

    As your conf is .oauth2ResourceServer().jwt(), populate your test security context with a JwtAuthenticationToken using SecurityMockMvcRequestPostProcessors.jwt().

    Your test should be no more complicated than that:

    @WebMvcTest(CodeController.class)
    @Import({ SecurityConfig.class })
    class CodeControllerTests {
    
        @MockBean
        private CodeExecutionService codeExecutionService;
    
        @MockBean
        private ProblemService problemService;
    
        @MockBean
        private TestCaseValidationService validationService;
    
        @Autowired
        MockMvc mockMvc;
    
        @Test
        void runTestCode() throws Exception {
            RunCodeDTO runCodeDTO = new RunCodeDTO("python", "something", "two-sum");
            Problem problem = ProblemUtils.getTwoSum();
            UserCode userCode = new UserCode(runCodeDTO.code(), runCodeDTO.language());
            userCode.mergeWithStub(problem.getCodeRunStub());
    
            List<TestResult> testResults = problem.getRunTestCases()
                    .stream()
                    .map(testCase -> new TestResult(testCase, Status.SUCCESS, ""))
                    .toList();
    
            List<TestOutput> testOutputs = testResults
                    .stream()
                    .map(result -> new TestOutput(result.testCase(), new ValidationResult(Status.SUCCESS, "Test Case Passed")))
                    .toList();
            when(problemService.getProblem(runCodeDTO.problemId())).thenReturn(Optional.of(problem));
            when(codeExecutionService.executeAllTestCases(problem.getRunTestCases(), userCode)).thenReturn(testResults);
            when(validationService.validateAllTestResults(testResults, problem.getOutputType(), problem.getValidationType())).thenReturn(testOutputs);
    
            mockMvc
                .perform(MockMvcRequestBuilders.post("/code/test")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content("")
                    .with(SecurityMockMvcRequestPostProcessors.jwt()
                        .jwt(jwt -> jwt.claim(StandardClaimNames.SUB, "Tonton Pirate"))
                        .authorities(List.of(new SimpleGrantedAuthority("NICE"), new SimpleGrantedAuthority("AUTHOR")))))
                .andExpect(status().isOk());
        }
    }
    

    Instead of SecurityMockMvcRequestPostProcessors.jwt(), you could also use @WithMockJwtAuth available from spring-addons-oauth2-test with sources available on this repo of mine (repo which also contains plenty of resource-server samples, each with access-control unit-tests)