2018年8月23日 星期四

[Java] test Jersey APIs

如果 APIs 裡面用了大量過濾器、constraint,一般的單元測試很難一併測試,Jersey 提供了測試框架就可以很容易一起測試。 其實就是跑一個伺服器起來去呼叫 API。

// build.gradle
dependencies {
    testCompile (
        'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.27'
    )
}

因為我用 Junit5,所以需要稍微調整一下。

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ResourceTest extends JerseyTest {

    @Override
    protected Application configure() {
        enable(TestProperties.DUMP_ENTITY);
        enable(TestProperties.LOG_TRAFFIC);
        return new ResourceConfig(HelloResource.class);
    }

    @Override
    @BeforeEach
    public void setUp() throws Exception {
        // patch junit4
        super.setUp();
    }

    @Override
    @AfterEach
    public void tearDown() throws Exception {
        // patch junit4
        super.tearDown();
    }

    @Test
    void test() {
        Response response = target().path("/hello").request().get(); 
        Assertions.assertEquals(200, response.getStatus());
    }
}

參考資料:

2018年8月22日 星期三

[Java] Jersey 使用 javax.validation.constraints.*

常常會需要使用 @NotNull、@NotBlank,但是不會運作,參考官網之後的做法:

// build.gradle
dependencies {
    compile (
        'org.glassfish.jersey.ext:jersey-bean-validation:2.27',
        'org.hibernate:hibernate-validator:6.0.12.Final',
        'javax.el:javax.el-api:3.0.0',
    )
}

設定回傳訊息。

    @GET
    @Path("/hello")
    public Response getMessage(@NotBlank(message = "empty name") @QueryParam("name") String name) { ... }

開啟變數,改成自己處理錯誤,不使用 Servlet server 回傳訊息。

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;

@ApplicationPath("/")
public class WebApplication extends ResourceConfig {

    public WebApplication() {
        ...
        // ValidationExceptionMapper catchable
        property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
    }
}

import java.util.List;
import java.util.stream.Collectors;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class ValidationExceptionMapper implements ExceptionMapper {

    @Override
    public Response toResponse(ValidationException e) {
        List msgs = null;
        if (e instanceof ConstraintViolationException) {
            List msgs = ((ConstraintViolationException) e).getConstraintViolations()
                    .stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.toList());
        } else {
            ...
        }

        return Response.status(Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON).entity(msgs).build();
    }

}

參考資料:

2018年8月19日 星期日

[Java] use JavaDoc to generate Swagger resource

Swagger 寫在注解讓整個代碼變得很複雜,所以找到有人寫在 JavaDoc 的方式,首先 Gradle 的設定如下:

// build.gradle
apply plugin: 'java'

configurations {
    doclet
}

dependencies {
    doclet (
        'com.tenxerconsulting:swagger-doclet:1.1.3',
        'javax.ws.rs:javax.ws.rs-api:2.1'
    )
}

task generateRestApiDocs(type: Javadoc) {
    source = sourceSets.main.allJava
    destinationDir = reporting.file('rest-api-docs')
    options.classpath = configurations.doclet.files.asType(List)
    options.docletpath = configurations.doclet.files.asType(List)
    options.doclet = 'com.tenxerconsulting.swagger.doclet.ServiceDoclet'
    options.addStringOption('apiVersion', '1')
    options.addStringOption('docBasePath', "/$project.name/static/swagger-ui")
    options.addStringOption('apiBasePath', "/$project.name")
    options.addBooleanOption('skipUiFiles', false)
}

public final class Hello {

    /**
     * Get user toggle.
     *
     * @param name your name
     * @return http response
     * @requiredParams name
     * @paramsDefaultValue name world
     * @responseType String
     * @responseMessage 200 ok `String
     * @responseMessage 500 internal error `javax.ws.rs.WebApplicationException
     */
    @GET
    @Path("/{name}")
    public Response getHello(@PathParam("name") String name) {
        return Response.Ok("Hello " + name).build();
    }
}

參考資料:

[Java] JavaDoc cannot find Lombok symbols

使用 Lombok 之後,要產生 JavaDoc 會出現以下的警告訊息:

...
Foo.java:3: error: cannot find symbol
@Log4j2
 ^
  symbol: class Log4j2
javadoc: warning - Class Data not found.
javadoc: warning - Class AllArgsConstructor not found.
javadoc: warning - Class Getter not found.
javadoc: warning - Class Log4j2 not found.

一個簡單的解決方法就是把 Lombok 標記解譯,

// build.gradle
apply plugin: 'io.franzbecker.gradle-lombok' version '1.8'

import io.franzbecker.gradle.lombok.task.DelombokTask
task delombok(type: DelombokTask) {
    ext.outputDir = file("$buildDir/delombok")
    outputs.dir(outputDir)
    sourceSets.main.java.srcDirs.each {
        inputs.dir(it)
        args(it, "-d", outputDir)
    }
}

javadoc {
    dependsOn delombok
    source = delombok.outputDir
    failOnError = false
}

參考資料:

2018年8月2日 星期四

[Java] test Jersey injection

參考 StackOverflow:

// src/test/java/example/HellpResourceTest.java
package example;

import javax.inject.Inject;
import javax.ws.rs.core.Response;

import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.Binder;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import example.HelloResource;

class HelloResourceTest {
    @Inject
    private HelloResource resource;
    private ServiceLocator locator;

    @BeforeEach
    void setUp() {
        Binder resourceBinder = new AbstractBinder() {
            @Override
            public void configure() {
                // bind resource
                bindAsContract(HelloResource.class);
                // bind mock repository
                bind(new MockHelloRepository()).to(HelloRepository.class);
            }
        };
        locator = ServiceLocatorUtilities.bind(repositoryBinder, resourceBinder);
        locator.inject(this);
    }

    @AfterEach
    void tearDown() {
        if (locator != null) {
            locator.shutdown();
        }
    }

    @Test
    @DisplayName("Test Hello API")
    void doTest() {
        Assertions.assertNotNull(resource);
        Response response = resource.getHello("World");
        System.out.println(response);
        Assertions.assertEquals("Hello World", response);
    }

}

用到 org.glassfish.hk2:hk2-locator:2.5.0, org.glassfish.hk2:hk2-utils:2.5.0

參考資料:

[Java] JUnit with DynamoDBLocal

先設定 build.gradle:

// build.gradle
repositories {
    maven {
        // dynamodb local url
        url 'https://s3-us-west-2.amazonaws.com/dynamodb-local/release'
    }

    testCompile (
        'org.junit.jupiter:junit-jupiter-api:5.2.0',
        'com.amazonaws:DynamoDBLocal:1.11.119'
    )

    testRuntime (
        // junit5
        'org.junit.jupiter:junit-jupiter-engine:5.2.0',
        // dynamodb local
        'com.almworks.sqlite4java:libsqlite4java-linux-i386:1.0.392',
        'com.almworks.sqlite4java:libsqlite4java-osx:1.0.392'
    )
}
// link native sqlite4java
task copyNativeDeps(type: Copy) {
    from (configurations.testCompile) {
        include '*.dylib'
        include '*.so'
        include '*.dll'
    }
    into 'build/libs'
}

test {
    useJUnitPlatform()
    dependsOn copyNativeDeps
    doFirst {
        // setup sqlite4java path
        systemProperty 'java.library.path', 'build/libs'
    }
    testLogging {
        showStandardStreams = true
    }
}

接下來就是測試:

package example;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

public class DynamoDBLocalTest {

    @Test
    public void createTable() throws Exception {
        AmazonDynamoDB client = DynamoDBEmbedded.create().amazonDynamoDB();
        DynamoDB dynamodb = new DynamoDB(client);
        String tableName = "Employee";

        List keySchema = new ArrayList();
        keySchema.add(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH));

        List attributeDefinitions = new ArrayList();
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("Name").withAttributeType("S"));

        CreateTableRequest request = new CreateTableRequest().withTableName(tableName).withKeySchema(keySchema)
                .withProvisionedThroughput(
                        new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L))
                .withAttributeDefinitions(attributeDefinitions);

        System.out.println("Issuing CreateTable request for " + tableName);
        Table table = dynamodb.createTable(request);
        System.out.println("Waiting for " + tableName + " to be created...this may take a while...");
        table.waitForActive();

        System.out.println(dynamodb.getTable(tableName).describe());
    }

}

參考資料:

系列:

[Java] Invalid HTTP method: PATCH

最近系統需要使用 Netty4,所以把衝突的 Netty3 拆掉,然後就出現了例外。 pom.xml <dependency> <groupId>com.ning</groupId> <artifactId>as...