Search code examples
javaspring-boottestingspring-securityspring-cloud-contract

Spring Cloud contract tests fail due to NoClassDefFound


First of all I would like to state that I'm fairly new to Spring Boot and Spring Cloud.

I have the following project setup:

  • Consumer Driven Contracts are stored in a Git repository following this guide. Producer has to answer to two consumers accessing two different endpoints, so there is no conflict there.
  • Producer is setup following the same guide to fetch contracts from the remote.
  • Both projects are set up using Maven.

Dependencies on the producer side are:

  • Spring Boot 3.0.2
  • Spring Cloud Contract Verifier 4.0.1
  • Spring Cloud Contract Maven Plugin 4.0.1
  • Spring Security 6.0.1 and Spring Security Test (unrelated but probably important)

When using the routines spring-cloud-contract:generateTests everything runs fine and tests are generated in the proper directory, then when running the lifecycle routine test and those generated contract tests are run they produce the following error:

NoClassDefFoundError: javax/servlet/Filter

                at com.projectId.artifactId.contracts.ConsumerTest.validate_shouldReturnApplicationRoles(ConsumerTest.java:113)

projectId, artifactId, ConsumerTest are placeholders for the real package, artifact name and consumer name respectively. Sample of the method where it fails (all generated tests fail and give the same error, line 113 in this case refers to the point of get("/auth/v1/role")):

public class ConsumerTest extends BaseTestClass {
    @Test
    public void validate_shouldReturnApplicationRoles() throws Exception {
       // given:
          MockMvcRequestSpecification request = given();
    
    
       // when:
          ResponseOptions response = given().spec(request)
                .get("/auth/v1/role");
    
       // then:
          assertThat(response.statusCode()).isEqualTo(200);
          assertThat(response.header("Content-Type")).matches("application/json.*");
          assertThat(response.header("Access-Control-Allow-Origin")).isEqualTo("*");
    
       // and:
          DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
          assertThatJson(parsedJson).array().contains("['id']").isEqualTo(1);
          assertThatJson(parsedJson).array().contains("['name']").isEqualTo("Acotado");
          assertThatJson(parsedJson).array().contains("['description']").isEqualTo("Acceso acotado en un espacio temporal desde que se crea la clave");
          assertThatJson(parsedJson).array().contains("['id']").isEqualTo(2);
          assertThatJson(parsedJson).array().contains("['name']").isEqualTo("Ilimitado");
          assertThatJson(parsedJson).array().contains("['description']").isEqualTo("Acceso ilimitado");
    }
}
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("test")
public abstract class BaseTestClass {
    @Autowired
    AuthorizationController authorizationController;

    @Autowired
    IdentityController identityController;

    @MockBean
    AuthorizationService authorizationService;

    @MockBean
    IdentityService identityService;

    @BeforeEach
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(authorizationController, identityController);
    }
}

The test profile just configures a context path and a h2 instance for the integration tests of the application.

I have tried different BaseClasses for tests and expected the tests to pass or at least to fail in a meaningful way. What is failing here? I have a suspicion that Spring Security has something to do with that error...

Update

On an empty project with only Spring Boot and Spring Cloud Contract as dependencies the test do run and sure do fail but as expected in a meaningful way (comparing responses expected against obtained). BaseClass does differ a little bit since on this trial scenario there is only a mock controller.

Update 2

I've added Spring Security to the aforementioned mock project and that has been enough to make it fail to compile. I have tried with two BaseTestClass definitions, one following Spring Cloud Contract's guidelines (which is the one that worked previously) and another following Spring Security's guides on MockMvc, both below by mention order:

class BaseTestClass {
    @BeforeAll
    public static void setup() {
        RestAssuredMockMvc.standaloneSetup(new TrialController);
    }
}
class BaseTestClass{
    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new TrialController()).build();
    }
}

Neither manages to run the application nor the test producing either IllegalState errors or spring test delegate cannot be null.


Solution

  • Okay so after countless hours of going back and forth through the many (and confusing) Spring Docs pages and guides and reading many almost related SO questions and answers here is how I got the application and the tests running.

    The BaseTestClass should look like this:

    import io.restassured.module.mockmvc.RestAssuredMockMvc;
    import org.junit.jupiter.api.BeforeAll;
    import org.junit.jupiter.api.TestInstance;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.web.FilterChainProxy;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.web.servlet.MockMvc;
    import org.springframework.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
    
    @SpringBootTest
    @ActiveProfiles("test")
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    public abstract class BaseTestClass {
        private MockMvc mvc;
    
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        @Autowired
        FilterChainProxy springSecurityFilterChain;
    
        @BeforeAll
        public void setup() {
            mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                     .apply(springSecurity(springSecurityFilterChain))
                     .build();
            RestAssuredMockMvc.mockMvc(mvc);
        }
    }
    

    The ActiveProfiles annotation is just necessary if you need any application configuration for the tests to run, in my case the connection to an h2 instance.

    The TestInstance annotation is to allow the BeforeAll annotated method not to be static in the same fashion as the docs use JUnit4's Before.

    Hope it helps somebody else.