All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rocks.bastion.Bastion Maven / Gradle / Ivy

package rocks.bastion;

import rocks.bastion.core.Assertions;
import rocks.bastion.core.BastionFactory;
import rocks.bastion.core.HttpRequest;
import rocks.bastion.core.builder.BastionBuilder;
import rocks.bastion.core.builder.ExecuteRequestBuilder;
import rocks.bastion.core.builder.PostExecutionBuilder;
import rocks.bastion.core.configuration.Configuration;
import rocks.bastion.core.configuration.GlobalRequestAttributes;
import rocks.bastion.core.resource.ResourceLoader;

import static java.util.Objects.requireNonNull;

/**
 * The main starting point for creating a Bastion test using the library.
 * 

Overview

*

* Bastion is a library that eases the development of end-to-end tests for HTTP APIs. A typical user would write tests using * Bastion based off of some sort of API specification. This API specification can be anything like a WADL file, RAML file or a JSON schema. * A test engineer would prepare requests and responses based on these specifications to test the overall process of calling these APIs. *

*

* Bastion also contains tools that will help developers fix problems that occur as fast as possible. When a test fails, the entire * request and response content is logged. Also, when using the built-in assertions, helpful diff text is displayed related to the type * of HTTP response you're expecting. *

*

Creating a Bastion Test

*

* Bastion is run as part of your standard testing framework. For an * example of a Bastion test, see the Bastion User Guide. *

*

* Inside your test method or test case, make a call to the {@link Bastion#request(String, HttpRequest)} method. This will * return a fluent-builder style interface that will allow you * to further specify the response model and assertions on your test. *

*

* The returned builder will allow you call any of the following methods: *

*
    *
  • {@link rocks.bastion.core.builder.BindBuilder#bind(Class)}: Specify which class type to use when constructing a model of the received response. Bastion will * interpret and decode the received response object into an instance of whatever type is supplied to this method.
  • *
  • {@link rocks.bastion.core.builder.AssertionsBuilder#withAssertions(Assertions)}: Specify what test assertions to apply to the response. We recommend supplying the * assertions as a lambda or using one of the available subclass implementations of {@link Assertions}.
  • *
  • {@link ExecuteRequestBuilder#call()}: Starts the Bastion test by executing the HTTP request.
  • *
*

* You cannot call any of the methods above before any of the methods listed before it. Therefore, in your test, you should call * the methods above one after each other as listed above: you can skip any of the methods and Bastion will assign defaults. * For example, if you want to make an HTTP request and apply some assertions without binding a model, you would * call the {@link #request(String, HttpRequest)} method first, followed by the {@link rocks.bastion.core.builder.AssertionsBuilder#withAssertions(Assertions)} * method to configure your assertions, followed by the {@link ExecuteRequestBuilder#call()} method to send the request * and start the test. *

*

Getting the Response

*

* Once you execute the HTTP test using the {@link ExecuteRequestBuilder#call()} method, you can retrieve the HTTP response * and any views decoded by Bastion using the following methods: *

*
    *
  • {@link PostExecutionBuilder#getResponse()}: Get the HTTP response object that was returned following the request * sent with the Bastion test. This will contain the status code, and HTTP headers and the body content. It will also * contain the model object decoded by Bastion.
  • *
  • {@link PostExecutionBuilder#getModel()}: Get the model object decoded by Bastion from the HTTP response. You can specify the * type of model object you expect using {@link rocks.bastion.core.builder.BindBuilder#bind(Class)}.
  • *
  • {@link PostExecutionBuilder#getView(Class)}: Get alternative view objects (different than the model) of the response.
  • *
*

* When Bastion receives a response, it will attempt to decode it into as many view objects as possible. A view is a Java object which * represents the data received inside the response body. For example, if the response is a JSON string, then one of the views would be a * {@link com.fasterxml.jackson.databind.JsonNode}. *

*

* The model object is a plain Java object that was created after Bastion has decoded the content body into views. If you specified * a model type with the {@link rocks.bastion.core.builder.BindBuilder#bind(Class)} method, then the model object will be * of the type you specified. *

*

Obtaining the response in these ways is useful especially if you want to perform another Bastion request after in the same * test. You can use the information retrieved in the model, views or response for the next Bastion request.

*

Request Types

*

* There are multiple types of requests that you can send using Bastion. To start building a Bastion test, construct and initialise * a request and pass it to the {@link #request(String, HttpRequest)} method. The types of {@link HttpRequest requests} you can * use are listed below (see each requests's JavaDocs for more information about how to use that request): *

*
    *
  • {@link rocks.bastion.core.GeneralRequest}: Allows you to specify a string content-body. Can also be used if you wouldn't * like to send any content-body (such as for a {@code GET} request).
  • *
  • {@link rocks.bastion.core.FileRequest}: Allows you to string content-body that is loaded from a file.
  • *
  • {@link rocks.bastion.core.FormUrlEncodedRequest}: Allows you to construct an HTTP request containing URL encoded form data in its * content-body.
  • *
  • {@link rocks.bastion.core.json.JsonRequest}: Allows you to specify a valid JSON string to send as part of your HTTP request * content-body.
  • *
*

Also, feel free to provide your own implementation of an {@link HttpRequest} which will allow you customise completely * the request sent by Bastion. Implementing your own {@link HttpRequest request class} is extremely useful to encourage * reuse and maintainability (instead of repeating your own requests each time).

*

Assertion Types

*

* Assertions allow you to quickly check whether a test was successful or not. Note that a request does not necessarily have * to succeed for a test to pass. In fact, you are encouraged to test your error/failure paths extensively as well as your * success paths of your APIs to ensure good quality software. Bastion allows you to do this easily by supply an {@link Assertions} * object to the {@link rocks.bastion.core.builder.AssertionsBuilder#withAssertions(Assertions)}} method. At the moment, there * is only one built-in {@link Assertions assertion type} listed below (see the type's JavaDocs for more information on * how to use it): *

*
    *
  • {@link rocks.bastion.core.StatusCodeAssertions}: Expects a response from the remote server to have any of the specified status codes.
  • *
  • {@link rocks.bastion.core.json.JsonResponseAssertions}: Expects a JSON response from the remote server. It allows * you to specify a valid JSON string to assert the response against. The assertion is smart in that a JSON structural comparison * is performed (instead of a straight-up text equality comparison) making sure that the response contains all the expected * properties, with the correct values, regardless of whitespace and property order.
  • *
  • {@link rocks.bastion.core.json.JsonSchemaAssertions}: Expects a JSON response from the remote server to adhere to the given JSON schema.
  • *
*

* Just like requests, you may define your own {@link Assertions assertion types}. You are encouraged to do so if you * are performing some sort of assertion frequently throughout your tests. This enables code reuse and improves the maintainability * of your tests. *

*

* If you do not want to implement your own assertion classes you can supply an assertions object directly into the * {@link rocks.bastion.core.builder.AssertionsBuilder#withAssertions(Assertions)} method of the Bastion test builder as * a Java 8 lambda. *

*

Global configuration

*

* You can specify global attributes which apply to all future Bastion requests using the {@link Bastion#globals()} method. This allows * you to add global {@link GlobalRequestAttributes#addQueryParam(String, String) query parameters}, * {@link GlobalRequestAttributes#addHeader(String, String) headers} and * {@link GlobalRequestAttributes#addRouteParam(String, String) route parameters} to all requests you make using Bastion. You can also * {@link GlobalRequestAttributes#setGlobalRequestTimeout(long) configure the timeout} which applies to requests. *

*

Groovy Tests

*

* Certain features of Bastion such as the {@link rocks.bastion.core.json.JsonRequest} and the {@link rocks.bastion.core.json.JsonResponseAssertions} * classes work with strings. These string inputs will frequently turn out to be extremely long and, especially in Java, * this will cause your strings to appear unwieldy due to multiple breaklines and escape characters. Fortunately, Bastion * works with any of the JVM languages including Groovy. *

*

* Groovy is easy to set up for your project. After you have set up Groovy to run automated tests, simply add Bastion to * your project's dependencies (as explained above) and create your tests as normal. With Groovy, you can supply long strings * using alternate string delimiters including so-called multiline strings. These will improve the readability of your code * greatly when supplying strings to Bastion. See the Bastion User Guide * for an example of how a Bastion test would look like in Groovy. *

*/ public final class Bastion { /** *

* Starts building a single Bastion test which will execute the specified HTTP request. The first parameter is a descriptive * string that appears in test reports and any UI running your tests. The method will return a fluent-builder object which * will let you specify the test further. *

*

* The request you specify in this method can be an instance of one of the in-built {@link HttpRequest request types} * provided with Bastion itself. You can also supply your own implementation of a request by subclassing {@link HttpRequest}. *

* * @param message A descriptive message for this Bastion test. * @param request The HTTP request that Bastion will execute for this test. * @return A fluent-builder object which will let you bind a model type, add assertions and execute the test. */ public static BastionBuilder request(String message, HttpRequest request) { return BastionFactory.getDefaultBastionFactory().getBastion(message, request); } /** *

* Starts building a single Bastion test which will execute the specified HTTP request. The method will return a * fluent-builder object which will let you specify the test further. *

*

* The request you specify in this method can be an instance of one of the in-built {@link HttpRequest request types} * provided with Bastion itself. You can also supply your own implementation of a request by subclassing {@link HttpRequest}. *

* * @param request The HTTP request that Bastion will execute for this test. * @return A fluent-builder object which will let you bind a model type, add assertions and execute the test. */ public static BastionBuilder request(HttpRequest request) { return BastionFactory.getDefaultBastionFactory().getBastion("", request); } /** *

* Loads Bastion's configuration from the provided resource location. The resource location should be a valid .yml file that * corresponds to the same schema as a {@link Configuration}. *

* * @see Configuration * @see ResourceLoader * @param resourceLocation The resource location for the Bastion configuration. * @return The loaded configuration. */ public static Configuration loadConfiguration(String resourceLocation) { requireNonNull(resourceLocation, "The resource location cannot be null."); return BastionFactory.loadConfiguration(resourceLocation); } /** *

* Starts building or modifying the configuration of the {@link GlobalRequestAttributes} for Bastion. *

* * @return The configured global request attributes. */ public static GlobalRequestAttributes globals() { return BastionFactory.getDefaultBastionFactory().getConfiguration().getGlobalRequestAttributes(); } private Bastion() { // This class should not be instantiated. } }