I have the following code used in Junit 5 test:
@WebAppConfiguration
public class BaseControllerTest {
static {
System.setProperty("org.jboss.logging.provider", "slf4j");
}
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build/generated-snippets");
}
.......
public class StandaloneControllerGetByIdTest
extends BaseStandaloneControllerTest {
@Test
public void lookupStandaloneReturnsIsSuccessful() throws Exception {
mockMvc.perform(
get("/accounts", "123", "04d71b9379ef4db49c28e113485ea76d")
.contentType(APPLICATION_JSON_VALUE))
.andDo(print()).andExpect(status().isOk());
}
}
I get error:
org/junit/rules/TestRule
java.lang.NoClassDefFoundError: org/junit/rules/TestRule
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at java.base/java.lang.Class.getDeclaredFields0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3473)
at java.base/java.lang.Class.getDeclaredFields(Class.java:2542)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.ClassNotFoundException: org.junit.rules.TestRule
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 13 more
Full code:
org/junit/rules/TestRule
is for Junit 4. I need to use JUnit 5 dependencies. Do you know how I can fix this issue in Junit 5?
Root Cause:
You're getting java.lang.NoClassDefFoundError: org/junit/rules/TestRule
because you're using JUnitRestDocumentation
, which depends on JUnit 4's TestRule
, but you only have JUnit 5 (junit-jupiter) in your project.
How to change it with JUnit5:
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
@ExtendWith(RestDocumentationExtension.class)
public class MyTest {
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
mockMvc = MockMvcBuilders.standaloneSetup(myController)
.apply(documentationConfiguration(restDocumentation))
.build();
}
}
Here is the full example of the RestDocumentationExtension
usage: (https://github.com/spring-projects/spring-restdocs/blob/v2.0.2.RELEASE/samples/junit5/src/test/java/com/example/junit5/SampleJUnit5ApplicationTests.java);
In your case you have to do:
build.gradle
file:plugins {
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
id 'org.asciidoctor.jvm.convert' version '2.4.0' // add this plugin
}
group = 'com.private.class.poc'
version = '0.0.1'
tasks.bootJar {
archiveFileName.set('private-class-poc.jar')
}
java {
sourceCompatibility = '21'
}
ext {
set('springCloudVersion', "2024.0.0")
set('snippetsDir', file("build/generated-snippets")) // package name for snippets
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'com.google.guava:guava:33.2.1-jre'
implementation 'commons-validator:commons-validator:1.9.0'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'commons-io:commons-io:2.18.0'
implementation 'org.springframework.boot:spring-boot-starter-quartz'
implementation 'com.newrelic.agent.java:newrelic-api:8.11.1'
implementation 'org.eclipse.jetty:jetty-util:12.0.16'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.apache.httpcomponents.client5:httpclient5'
implementation 'commons-jxpath:commons-jxpath:1.3'
implementation 'com.google.code.gson:gson:2.12.1'
implementation 'com.codahale.metrics:metrics-core:3.0.2'
implementation 'com.ryantenney.metrics:metrics-spring:3.1.3'
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc"
testImplementation 'io.rest-assured:rest-assured:5.5.1'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
outputs.dir snippetsDir // set output directory
useJUnitPlatform()
}
tasks.named('asciidoctor') { // add asciidoctor task
inputs.dir snippetsDir
dependsOn test
}
BaseControllerTest
:package com.test.core.web.rest.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import org.apache.commons.validator.routines.UrlValidator;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
public class BaseControllerTest {
// Remove the http:// part and port from here to have it properly documented.
// Otherwise you will have smth like: http://http://localhost:8011:8080/account...
protected static final String MOCK_MVC_HOST = "localhost";
// use port as separate variable
protected static final int MOCK_MVC_PORT = 8011;
static {
System.setProperty("org.jboss.logging.provider", "slf4j");
}
// !!! Remove JUnitRestDocumentation !!!
@Mock
protected UrlValidator urlValidator;
public BaseControllerTest() {
MockitoAnnotations.openMocks(this);
}
@BeforeEach
public void setupUrlValidatorMock() {
when(urlValidator.isValid(any())).thenReturn(true);
}
}
BaseStandaloneControllerTest
:package com.test.core.web.rest.controller.standalone;
import com.test.core.web.rest.controller.BaseControllerTest;
import com.test.core.web.rest.controller.ReturnReversalController;
import com.test.core.web.rest.controller.StandaloneCreditReturnController;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@DirtiesContext
@ExtendWith(RestDocumentationExtension.class) // add RestDocumentationExtension
public class BaseStandaloneControllerTest extends BaseControllerTest {
@InjectMocks
@Spy
public MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
public MockMvc mockMvc;
@Autowired
public WebApplicationContext webApplicationContext;
HttpHeaders headers = new HttpHeaders();
/*
* StandaloneCreditReturnController was changed to ReturnReversalController, because it contains
* the /accounts/{accountId}/{returnReversalId} endpoint, which you apparently want to test, whereas
* StandaloneCreditReturnController doesn't contain any endpoint.
*
* It was changed only for testing purposes, because StandaloneCreditReturnController is empty
*
* */
@InjectMocks
ReturnReversalController returnReversalController;
// RestDocumentationContextProvider restDocumentation will be automatically instantiated
@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
returnReversalController = new ReturnReversalController();
mockMvc = standaloneSetup(returnReversalController)
.apply(documentationConfiguration(restDocumentation).uris().withHost(MOCK_MVC_HOST).withPort(MOCK_MVC_PORT)
)
.build();
}
}
StandaloneControllerGetByIdTest
:package com.test.core.web.rest.controller.standalone;
import org.junit.jupiter.api.Test;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
public class StandaloneControllerGetByIdTest
extends BaseStandaloneControllerTest {
@Test
public void lookupStandaloneReturnsIsSuccessful() throws Exception {
/*
* 1. Url was updated to include path variables
* 2. Added package name, where the report will be added -> .andDo(document("home"))
*
* */
mockMvc.perform(
get("/accounts/{accountId}/{returnReversalId}", "123", "04d71b93-79ef-4db4-9c28-e113485ea76d")
.contentType(APPLICATION_JSON_VALUE))
.andDo(print())
.andDo(document("home")) // add directory to store reports for this test
.andExpect(status().isOk());
}
}
ReturnReversalController
:import com.test.core.gateway.domain.GatewayReturnReversalResponse;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RefreshScope
@RestController
public class ReturnReversalController {
// Added path variables to url
public static final String RETURN_REVERSAL_GET = "/accounts/{accountId}/{returnReversalId}";
@RequestMapping(method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE, value = RETURN_REVERSAL_GET)
@ResponseBody
@PreAuthorize("hasAuthority('ROLE_USER')")
public ResponseEntity<GatewayReturnReversalResponse> getReturnReversalById(
@PathVariable("accountId") String accountId,
@PathVariable("returnReversalId") UUID returnReversalId) {
GatewayReturnReversalResponse response = new GatewayReturnReversalResponse();
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
After running StandaloneControllerGetByIdTest
, you will find a full report in build/generated-snippets/home
: