Adding Tests
First, we should determine what are the external interfaces of the system to be tested. For the sake of simplicity, let's say it is a REST only system.
Let's, add qa-jgiven-rest to our project.
<dependencies>
...
<dependency>
<groupId>dev.aherscu.qa</groupId>
<artifactId>qa-jgiven-rest</artifactId>
<version>version</version>
<scope>test</scope>
</dependency>
...
</dependencies>
Environments
Now we should determine in which environments it should run. These may be called Development, QA, Staging… whatever. These are just groups of computers providing various services.
Under test/resources/environments
let's create a folder for each and put an
empty test.properties
file in each one. For each environment, we should ensure
there is Maven profile to allow its activation, like this:
<profiles>
...
<profile>
<id>environment-qa</id>
<properties>
<environment>qa</environment>
</properties>
</profile>
...
</profiles>
(a profile for Development environment is already defined in QA Testing Parent)
Later we should add properties to hold URLs, and credentials needed to access this system. These may look like this:
system.url=https://username:password@system-dev.host
token.url=https://oauth-dev.host
token.clientId=clientId
token.clientSecret=clientSecret
We should now have test/resources/environments/dev/test.properties
, and so on
for each environment.
Configuration
Somehow we need to access these properties at run time. Let's create
a TestConfiguration
class in the main package, like this:
public final class TestConfiguration extends BaseConfiguration {
// NOTE: this is imperative
public TestConfiguration(final Configuration... configurations) {
super(configurations);
}
public String systemUrl() {
return getString("system.url");
}
public SystemOAuthRequestFilter systemOAuthRequestFilter() {
return SystemOAuthRequestFilter.builder()
.refreshTokenUri(getString("token.url"))
.authorization(SystemOAuthRequestFilter.Authorization.builder()
.clientId(getString("token.clientId"))
.clientSecret(getString("token.clientSecret"))
.build())
.build();
}
}
Because there are many OAuth variations we need to specify exactly how to retrieve a token. It can be like this:
@SuperBuilder
@EqualsAndHashCode(callSuper = false)
@ToString
@Slf4j
public class SystemOAuthRequestFilter extends OAuthRequestFilter {
@Jacksonized
@Builder
@ToString
public static final class Authorization {
@JsonProperty("client_id")
public final String clientId;
@JsonProperty("client_secret")
public final String clientSecret;
@JsonProperty("grant_type")
public final String grantType = "client_credentials";
}
private final Authorization authorization;
@Override
protected TokenBlock retrieveTokenBlockFor(final ClientRequestContext context) {
log.debug("retrieving token block for {}", this);
try (val response = context
.getClient()
.target(refreshTokenUri)
.request()
.buildPost(json(authorization))
.invoke()) {
return response.readEntity(TokenBlock.class);
}
}
}
Common Scenario Stuff
In a REST scenario we need to initialize a client before and close it after, so instead of repeating ourselves in each scenario we can hold this in a common base class:
public abstract class SystemRestTest extends ConfigurableScenarioTest<TestConfiguration, RestScenarioType, SystemRestFixtures<?>, SystemRestActions<?>, SystemRestVerifications<?>> {
protected Client client; // we should manage the REST client
protected SystemRestTest() { // mandatory
super(TestConfiguration.class);
}
// so this one...
protected SystemRestTest(final Class<TestConfiguration> configurationType) {
super(configurationType);
}
// we may have sections that repeat themselves -- see below
public Sections sections() {
return new Sections();
}
public Sections sections(final String title) {
section(title);
return sections();
}
@AfterClass
protected void afterClassCloseRestClient() {
client.close();
}
@BeforeClass
protected void beforeClassOpenRestClient() {
client = LoggingClientBuilder.newClient();
}
public final class Sections {
public Sections creatingObject(final SomeObject object, final Name name) {
section("creating object with name");
when()
.creating(object)
.and().setting(name);
then()
.the_objects(adaptedStream(object -> object.name,
hasItemsMatching(equalTo(name))));
return this;
}
}
}
This will not compile because of missing:
SystemRestFixtures
,SystemRestActions
, andSystemRestVerifications
– these are the steps we should defineSomeObject
– this is a data model we should define too
One or More Scenarios
TBD
Steps
TBD
Next: Selenium Tests