Skip to main content

Overview

The Service class is the foundation of TNB’s testing framework. Every System-X service extends this abstract class, which provides a consistent interface for accessing accounts, clients, and validation helpers.

The Service abstraction

The Service class is a generic abstract class that manages three key components:
public abstract class Service<A extends Account, C, V extends Validation> 
    implements BeforeAllCallback, AfterAllCallback {
    
    protected A account;
    protected C client;
    protected V validation;
    
    public A account() {
        if (account == null) {
            Class<A> accountClass = (Class<A>) ReflectionUtil.getGenericTypesOf(Service.class, this.getClass())[0];
            account = AccountFactory.create(accountClass);
        }
        return account;
    }
    
    protected C client() {
        return client;
    }
    
    public V validation() {
        return validation;
    }
}
The Service class implements JUnit 5’s BeforeAllCallback and AfterAllCallback interfaces, enabling automatic lifecycle management when used with @RegisterExtension.

Service components

Each service consists of three parts:
1

Account

A Java object containing all information required to connect to the service (credentials, endpoints, configuration)
2

Client

A Java client used to access the service directly (may be a third-party SDK or custom implementation)
3

Validation

A wrapper around the client and account offering convenient methods for testing

Service types

TNB supports two categories of System-X services:
Remote services are internet-facing services that can be accessed publicly, such as:
  • Twitter
  • Salesforce
  • Cloud providers (AWS, Google Cloud, Azure)
These services don’t require deployment - they only establish a connection.

Example remote service

@AutoService(MyService.class)
public class MyService implements Service {
    private MyServiceAccount account;
    private MyServiceClient client;
    private MyServiceValidation validation;
    
    public MyServiceAccount account() {
        if (account == null) {
            account = AccountFactory.create(MyServiceAccount.class);
        }
        return account;
    }
    
    protected MyServiceClient client() {
        client = new MyServiceClient("https://myservice.com");
        return client;
    }
    
    public MyServiceValidation validation() {
        return validation;
    }
    
    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        validation = new MyServiceValidation(client(), account());
    }
    
    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        if (client != null) {
            client.close();
        }
    }
}

Creating services with ServiceFactory

The ServiceFactory class provides factory methods for creating service instances:
public class KafkaTest {
    @RegisterExtension
    public static Kafka kafka = ServiceFactory.create(Kafka.class);
    
    @Test
    public void testWithKafka() {
        final String topic = "myTopic";
        final String message = "Hello kafka!";
        kafka.validation().produce(topic, message);
        
        final List<ConsumerRecord<String, String>> records = kafka.validation().consume(topic);
        Assertions.assertEquals(1, records.size());
        Assertions.assertEquals(message, records.get(0).value());
    }
}

How ServiceFactory works

The ServiceFactory.create() method:
  1. Uses Java’s ServiceLoader to discover implementations
  2. If multiple implementations exist (e.g., LocalKafka and OpenshiftKafka), selects based on:
    • The test.use.openshift system property
    • Implementation priority
    • Enabled status
  3. Returns the appropriate instance
Relevant code from ServiceFactory.java:40-64:
if (loader.stream().count() == 1) {
    return loader.findFirst().get();
}

// Sort the services based on the priority and then return the first one that is enabled
Optional<S> service = StreamSupport.stream(loader.spliterator(), false)
    .sorted((s1, s2) -> ((Deployable) s2).priority() - ((Deployable) s1).priority())
    .filter(s -> ((Deployable) s).enabled())
    .findFirst();

Service lifecycle

Services automatically manage their lifecycle through JUnit 5 callbacks:

Using services in tests

Services expose three main methods for test interaction:
MethodDescriptionExample
account()Returns the account object with credentials and configurationkafka.account().basicUser()
client()Returns the underlying client (protected, for advanced usage)kafka.client()
validation()Returns the validation helper with convenient test methodskafka.validation().produce(topic, message)
In most cases, you’ll interact with services through the validation() method, which provides high-level testing utilities.

Next steps

Accounts

Learn about account management and credential loading

Validation

Understand validation classes and their role

Deployment modes

Explore local vs OpenShift deployment

Build docs developers (and LLMs) love