Skip to end of banner
Go to start of banner

REST API and Unit testing for back-end

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 12 Next »

Examples of tests written using following approach can be found here

Testing REST API:

Most of okapi back-end modules are covered with API tests using Vertx unit

Test setup:

Add runner:

@RunWith(VertxUnitRunner.class)

Run application (deploy verticle):

During test setup a verticle should be deployed to vertx instance (custom verticle class or RestVerticle class in case of using raml module builder)

private static Vertx vertx;
private static int port;


@BeforeClass
public static void setUpClass(final TestContext context) throws Exception {
Async async = context.async();
vertx = Vertx.vertx();
port = NetworkUtils.nextFreePort();

    //database setup goes here if necessary

  TenantClient tenantClient = new TenantClient("localhost", port, "diku", "dummy-token");
  DeploymentOptions restVerticleDeploymentOptions = new DeploymentOptions().setConfig(new JsonObject().put("http.port", port));
  vertx.deployVerticle(RestVerticle.class.getName(), restVerticleDeploymentOptions, res -> {
try {
tenantClient.post(null, res2 -> {
async.complete();
});
} catch (Exception e) {
e.printStackTrace();
}
});
}

Run database:

If test covers database interaction, database client should be configured.

Following code provides a possibility  to use existing database for tests or run database in embedded mode (slower):

useExternalDatabase = System.getProperty(
"org.folio.password.validator.test.database",
"embedded");

switch (useExternalDatabase) {
case "environment":
System.out.println("Using environment settings");
break;
case "external":
String postgresConfigPath = System.getProperty(
"org.folio.password.validator.test.config",
"/postgres-conf-local.json");
PostgresClient.setConfigFilePath(postgresConfigPath);
break;
case "embedded":
PostgresClient.setIsEmbedded(true);
PostgresClient.getInstance(vertx).startEmbeddedPostgres();
break;
default:
String message = "No understood database choice made." +
"Please set org.folio.password.validator.test.database" +
"to 'external', 'environment' or 'embedded'";
throw new Exception(message);
}

To configure database connection via environment variables:

  • set folio.password.validator.test.database property to 'environment';

  • set following environment variables:

    namevalue (example)
    db.hostlocalhost
    db.port5432
    db.databaseokapi_modules

    db.username

    folio_admin
    db.passwordfolio_admin

To configure database connection via file:

  • create file with configuration: 
    {
    "host" : "localhost",
    "port" : 5432,
    "database" : "okapi_modules",
    "username" : "folio_admin",
    "password" : "folio_admin"
    }
  • set folio.password.validator.test.database property to 'external';
  • put path to config file in property org.folio.password.validator.test.config;

Setting system property via cli parameters:

 -Dorg.folio.password.validator.test.database=external

 -Dorg.folio.password.validator.test.config=/postgres-conf.json

Running testing via maven:

Example (file with name "postgres-conf.json" is located in resources root):

mvn clean install -Dorg.folio.password.validator.test.database=external -Dorg.folio.password.validator.test.config=/postgres-conf.json

Running tests in IDE (intelliji idea example):

Mock HTTP calls:

WireMock framework allows to create and setup mock server:

Following code runs new mock server for every test on dynamic port.

To get port value userMockServer#port

Notifier logs all incoming call to mock server.

@org.junit.Rule
public WireMockRule userMockServer = new WireMockRule(
WireMockConfiguration.wireMockConfig()
.dynamicPort()
.notifier(new ConsoleNotifier(true)));

To mock response to specific request use template:

WireMock.stubFor(<request matching>.willReturn(<response definition>));

Example:

WireMock.stubFor(WireMock.get("/users?query=id==" + ADMIN_ID)
.willReturn(WireMock.okJson(someJsonResponse)
));

All <request matching> variants can be found in documentation Request Matching

Examples:

WireMock.post(WireMock.urlMatching("regexp here"))
.withRequestBody(WireMock.equalToJson("json here"))
WireMock.any(WireMock.urlMatching("regexp here"))
.withHeader("header", WireMock.equalTo("value"))
.withHeader("header2", WireMock.absent())
.withHeader("header3", WireMock.matching("regexp"))
.withQueryParam("param", WireMock.containing("substring))

All <response definition> variants can be found in documentation Response Templating

Examples:

WireMock.aResponse()
.withStatus(200)
.withHeader("response-header", "value")
.withBody(someByteArray);
WireMock.ok("bodyHere")
WireMock.okJson(someJsonResponse)
WireMock.notFound()

Perform REST API testing:

Rest-Assured provides a fluent API to create readable tests for REST resources:

RestAssured.given()
.param("limit", 20)
.when()
.get("blogs")
.then()
.statusCode(200);

One of the ways to use RestAssured is following:

RestAssured.given()
<building request>
.when()
<specifying method and path>
.then()
<response verification>

In <building request> section it is possible to specify headers, params, body of request.

In <specifying method and path> goes method and path specification.

In <response verification> result is verified.

A lot of best practices and use cases for testing REST API using RestAssured are described here

Unit testing:

This section does not describe unit testing approach in general but only some confusing cases about testing asynchronous calls.

Waiting and verifying asynchronous results:

Testing methods wtih Handler<AsyncResult> parameter:

@RunWith(VertxUnitRunner.class)

...

@Test
public void test(TestContext testContext) {
//some test setup

//expect
Handler<AsyncResult<JsonObject>> checkingHandler = testContext.asyncAssertSuccess(response -> {
//asserting goes here
Assert.assertThat(actual, Matchers.is(expected));
Mockito.verify(...);
});
someService.someAction(params, checkingHandler);
}

TestContext parameter should be added to the test method.
To expect failed asynchronous result use testContext#asyncAssertFailure

Mocking asynchronous method calls:

For example, in test it is necessary to mock some method that returns a result asynchronously. It takes Handler<AsyncResult> parameter to provide callback.

public interface DummyService {
boolean dummyAsyncCall(String someParameter, Handler<AsyncResult<String>> handler);
}

Suggested solution: use Mockito answers. Following class' constructor takes mock result that should be passed to handler as well as handler argument index.

public class GenericHandlerAnswer<H, R> implements Answer<R> {

private H handlerResult;
private int argumentIndex;
private R returnResult;

public GenericHandlerAnswer(H handlerResult, int handlerArgumentIndex) {
this.handlerResult = handlerResult;
this.argumentIndex = handlerArgumentIndex;
}

public GenericHandlerAnswer(H handlerResult, int handlerArgumentIndex, R returnResult) {
this(handlerResult, handlerArgumentIndex);
this.returnResult = returnResult;
}

@Override
public R answer(InvocationOnMock invocation) {
Handler<H> handler = invocation.getArgument(argumentIndex);
handler.handle(handlerResult);
return returnResult;
}
}

Example of test:

@RunWith(VertxUnitRunner.class)
public class MockDummyService {

@Mock
public DummyService dummyServiceMock;

@Before
public void setUp(TestContext testContext) throws Exception {
MockitoAnnotations.initMocks(this);
}

@Test
public void test(TestContext testContext) {
AsyncResult<String> dummyCallResult = Future.succeededFuture("Result for handler");
int handlerParameterIndex = 1;
boolean methodReturnedValue = true;

GenericHandlerAnswer<AsyncResult<String>, Boolean> answer =
new GenericHandlerAnswer<>(dummyCallResult, handlerParameterIndex, methodReturnedValue);
Mockito.doAnswer(answer)
.when(dummyServiceMock)
.dummyAsyncCall(ArgumentMatchers.anyString(), ArgumentMatchers.any());

Handler<AsyncResult<String>> checkingHandler = testContext.asyncAssertSuccess(response -> {
Assert.assertThat(response, Matchers.is(dummyCallResult.result()));
});
boolean result = dummyServiceMock.dummyAsyncCall("some string", checkingHandler);
Assert.assertTrue(result);
}
}

Useful links:

  • No labels