javaspringspring-bootspockjersey-test-framework

How can I configure JerseyTest with Spock in Spring Boot Application for Unit Tests?


I want to test the next controller class using Jersey Test in my Spring Boot application.

Controller Class

@RestController
@RequestMapping("/api/v1/demo")
public class UserController {

    private final IUserService service;

    public UserController(IUserService service) {
        this.service = service;
    }

    @PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<UserResponse> registerUser(@RequestBody @Valid UserRequest userRequest) {
        UserResponse user = service.createUser(userRequest);
        return ResponseEntity.status(CREATED).body(user);
    }
}

UserRequest Class

public class UserRequest {

    @NotBlank
    private String firstName;
    @NotBlank
    private String lastName;
    @NotBlank
    private String email;
    @NotBlank
    private String password;
    @NotBlank
    private String afm;
    @NotBlank
    private String birthDate;
    @NotBlank
    private String mobileNumber;
    @NotBlank
    private String cardNumber;

//getters and setters, no arg and full arg constructor

build.gradle

plugins {
    id 'java'
    id 'groovy'
    id 'org.springframework.boot' version '3.3.1'
    id 'io.spring.dependency-management' version '1.1.5'
}

group = 'com'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    runtimeOnly 'com.mysql:mysql-connector-j'
    compileOnly 'jakarta.platform:jakarta.jakartaee-api:11.0.0-M4'

    testImplementation 'org.eclipse:yasson'
    implementation 'org.glassfish.jersey.test-framework:jersey-test-framework-core:4.0.0-M1'
    implementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:4.0.0-M1'
    implementation 'org.glassfish.jersey.containers:jersey-container-grizzly2-http:4.0.0-M1'
    implementation 'org.glassfish.jersey.ext:jersey-bean-validation:4.0.0-M1'
    implementation 'org.glassfish.jersey.inject:jersey-hk2:4.0.0-M1'
    implementation 'org.glassfish.jersey.media:jersey-media-json-jackson:4.0.0-M1'
    implementation 'org.glassfish.jersey.core:jersey-client:4.0.0-M1'
    implementation 'org.glassfish.jaxb:jaxb-runtime:4.0.5'
    implementation 'jakarta.xml.bind:jakarta.xml.bind-api:3.0.1'
    testImplementation 'net.bytebuddy:byte-buddy'
    testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
    testImplementation 'org.apache.groovy:groovy-all:4.0.17'
    testImplementation 'org.spockframework:spock-core:2.4-M4-groovy-4.0'
    testImplementation 'org.spockframework:spock-spring:2.4-M4-groovy-4.0'
    testImplementation 'jakarta.json.bind:jakarta.json.bind-api'
    testImplementation 'org.eclipse:yasson'
    testImplementation 'io.github.joke:spock-mockable:2.3.2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'org.testcontainers:junit-jupiter'
    testImplementation 'org.testcontainers:mysql'
    testImplementation 'com.github.dasniko:testcontainers-keycloak:3.4.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

test {
    useJUnitPlatform()
}

task createTestResources() {
    copy {
        from 'env'
        include 'test.properties'
        into 'src/test/resources'
        expand project.properties
    }
}

clean.doFirst {
    delete 'src/test/resources/test.properties'
}

Below you can see the test along with the configuration Test class

class UserControllerSpec extends ResourceSpecification {

    static final String REGISTER_URL = '/api/v1/demo/register'

    @Subject
    UserController controller

    IUserService service

    @Override
    protected createResource() {
        service = Mock()
        controller = new UserController(service)
    }

    def 'Successfully calling the preview endpoint'() {
        given: 'a valid request'
        def request = TestFixtures.createUserRequest()

        and: 'an expected response'
        def expectedResponse = TestFixtures.createUserResponse()

        when: 'the controller is called'
        def response = jerseyTest.target(REGISTER_URL).request().post(Entity.json(request))

        then: 'response should have status 201'
        response.getStatus() == 201

        and: 'the cancel preview service is called once with the correct request parameters'
        1 * service.createUser({
            it.firstName == request.firstName &&
                    it.lastName == request.lastName &&
                    it.email == request.email &&
                    it.password == request.password &&
                    it.afm == request.afm &&
                    it.birthDate == request.birthDate &&
                    it.mobileNumber == request.mobileNumber &&
                    it.cardNumber == request.cardNumber
        }) >> expectedResponse

        and: 'the actual response should contain the expected values'
        def actualResponse = response.readEntity(UserResponse.class)
        actualResponse == expectedResponse
    }
}

Configuration Class

abstract class ResourceSpecification extends Specification {

    JerseyTest jerseyTest
    JsonSlurper jsonSlurper

    protected abstract createResource()

    def setup() {
        jerseyTest = new JerseyTest() {
            @Override
            protected Application configure() {
                new ResourceConfig()
                        .register(createResource())
            }
        }
        jerseyTest.setUp()
        jsonSlurper = new JsonSlurper()
    }

    def cleanup() {
        jerseyTest.tearDown()
    }
}

My test fails with the next error:

Jul 29, 2024 8:37:50 PM org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory$GrizzlyTestContainer <init>
INFO: Creating GrizzlyTestContainer configured at the base URI http://localhost:9998/
Jul 29, 2024 8:37:50 PM org.glassfish.jersey.internal.inject.Providers checkProviderRuntime
WARNING: A provider com.ebanking.system.controller.UserController registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider com.ebanking.system.controller.UserController will be ignored. 
20:37:51.233 [Test worker] INFO org.hibernate.validator.internal.util.Version -- HV000001: Hibernate Validator 8.0.1.Final
Jul 29, 2024 8:37:51 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:9998]
Jul 29, 2024 8:37:51 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jul 29, 2024 8:37:51 PM org.glassfish.grizzly.http.server.NetworkListener shutdownNow
INFO: Stopped listener bound to [localhost:9998]

Too few invocations for:

1 * service.createUser({
            it.firstName == request.firstName &&
                    it.lastName == request.lastName &&
                    it.email == request.email &&
                    it.password == request.password &&
                    it.afm == request.afm &&
                    it.birthDate == request.birthDate &&
                    it.mobileNumber == request.mobileNumber &&
                    it.cardNumber == request.cardNumber
        }) >> expectedResponse   (0 invocations)

Unmatched invocations (ordered by similarity):

None


Too few invocations for:

1 * service.createUser({
            it.firstName == request.firstName &&
                    it.lastName == request.lastName &&
                    it.email == request.email &&
                    it.password == request.password &&
                    it.afm == request.afm &&
                    it.birthDate == request.birthDate &&
                    it.mobileNumber == request.mobileNumber &&
                    it.cardNumber == request.cardNumber
        }) >> expectedResponse   (0 invocations)

Unmatched invocations (ordered by similarity):

None


    at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:110)
    at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:95)
    at com.ebanking.system.controller.UserControllerSpec.Successfully calling the preview endpoint(UserControllerSpec.groovy:33)

The ResourceSpecification class works well with OpenLiberty applications but it seems to me that it does not happen the same with Spring Boot ones. What am I missing? Any suggestions?


Solution

  • I am not entirely sure that you can work Jersey Test framework along with Spring Boot MCV Controllers. Jersey Test framework is used on JAX-RS applications.