CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-junit5-internal

A runner for unit tests, intended for testing Quarkus rather than for end user consumption.

Pending
Overview
Eval results
Files

build-chain-customization.mddocs/

Build Chain Customization

Support for customizing Quarkus build chains during testing, including build steps, producers, and consumers for comprehensive build testing.

ProdModeTestBuildStep

Abstract base class for creating custom build steps in production mode tests.

Core API

public abstract class ProdModeTestBuildStep implements BuildStep {
    
    public ProdModeTestBuildStep(Map<String, Object> testContext);
    public Map<String, Object> getTestContext();
}

ProdModeTestBuildChainBuilderConsumer

Consumer for customizing build chains in production mode tests.

Core API

public class ProdModeTestBuildChainBuilderConsumer implements Consumer<BuildChainBuilder> {
    
    public ProdModeTestBuildChainBuilderConsumer(String buildStepClassName, 
        List<String> producesClassNames, List<String> consumesClassNames, 
        Map<String, Object> testContext);
    
    public void accept(BuildChainBuilder builder);
}

ProdModeTestBuildChainCustomizerProducer

Function for producing build chain customizers.

Core API

public class ProdModeTestBuildChainCustomizerProducer implements Function<Map<String, Object>, List<Consumer<BuildChainBuilder>>> {
    
    public List<Consumer<BuildChainBuilder>> apply(Map<String, Object> testContext);
}

Usage Examples

Creating Custom Build Steps

import io.quarkus.test.ProdModeTestBuildStep;
import io.quarkus.deployment.builditem.BuildItem;
import io.quarkus.deployment.BuildContext;

public class MyCustomBuildStep extends ProdModeTestBuildStep {
    
    public MyCustomBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        // Access test context
        Map<String, Object> testContext = getTestContext();
        String customValue = (String) testContext.get("customKey");
        
        // Perform custom build logic
        MyBuildItem buildItem = new MyBuildItem(customValue);
        context.produce(buildItem);
    }
}

// Custom build item
public class MyBuildItem extends BuildItem {
    private final String value;
    
    public MyBuildItem(String value) {
        this.value = value;
    }
    
    public String getValue() {
        return value;
    }
}

Using Build Steps in Tests

import io.quarkus.test.QuarkusProdModeTest;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.Test;

public class BuildChainCustomizationTest {
    
    @RegisterExtension
    static final QuarkusProdModeTest config = new QuarkusProdModeTest()
        .withApplicationRoot(jar -> jar.addClasses(MyService.class))
        .addBuildChainCustomizerEntries(new QuarkusProdModeTest.BuildChainCustomizerEntry(
            MyCustomBuildStep.class,
            List.of(MyBuildItem.class),  // Produces
            List.of(ApplicationArchivesBuildItem.class)  // Consumes
        ));
    
    @Test
    public void testCustomBuildStep() {
        // Test that custom build step was executed
        // Build artifacts will include MyBuildItem
    }
}

Complex Build Step with Dependencies

public class ComplexBuildStep extends ProdModeTestBuildStep {
    
    public ComplexBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        // Consume required build items
        ApplicationArchivesBuildItem archives = context.consume(ApplicationArchivesBuildItem.class);
        FeatureBuildItem features = context.consume(FeatureBuildItem.class);
        
        // Access test context for configuration
        Map<String, Object> testContext = getTestContext();
        boolean enableFeature = (Boolean) testContext.getOrDefault("enableFeature", false);
        
        if (enableFeature) {
            // Produce custom build items
            context.produce(new CustomFeatureBuildItem());
            context.produce(new AdditionalBeanBuildItem(MyCustomBean.class));
        }
        
        // Modify existing build items if needed
        for (ApplicationArchive archive : archives.getAllApplicationArchives()) {
            // Process archive
        }
    }
}

Multi-Step Build Chain

public class FirstBuildStep extends ProdModeTestBuildStep {
    public FirstBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        context.produce(new IntermediateBuildItem("processed-data"));
    }
}

public class SecondBuildStep extends ProdModeTestBuildStep {
    public SecondBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        IntermediateBuildItem intermediate = context.consume(IntermediateBuildItem.class);
        String processedData = intermediate.getData();
        
        // Final processing
        context.produce(new FinalBuildItem(processedData + "-final"));
    }
}

// Test with multiple build steps
@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
    .withApplicationRoot(jar -> jar.addClasses(MyService.class))
    .addBuildChainCustomizerEntries(new QuarkusProdModeTest.BuildChainCustomizerEntry(
        FirstBuildStep.class,
        List.of(IntermediateBuildItem.class),
        List.of()
    ))
    .addBuildChainCustomizerEntries(new QuarkusProdModeTest.BuildChainCustomizerEntry(
        SecondBuildStep.class,
        List.of(FinalBuildItem.class),
        List.of(IntermediateBuildItem.class)
    ));

Conditional Build Steps

public class ConditionalBuildStep extends ProdModeTestBuildStep {
    
    public ConditionalBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        Map<String, Object> testContext = getTestContext();
        
        // Check conditions from test context
        String environment = (String) testContext.get("environment");
        boolean debugMode = (Boolean) testContext.getOrDefault("debug", false);
        
        if ("production".equals(environment)) {
            context.produce(new ProductionOptimizationBuildItem());
        } else if ("development".equals(environment)) {
            context.produce(new DevelopmentToolsBuildItem());
        }
        
        if (debugMode) {
            context.produce(new DebugInfoBuildItem());
        }
    }
}

// Usage in test
@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
    .withApplicationRoot(jar -> jar.addClasses(MyService.class))
    .addBuildChainCustomizerEntries(new QuarkusProdModeTest.BuildChainCustomizerEntry(
        ConditionalBuildStep.class,
        List.of(ProductionOptimizationBuildItem.class, DevelopmentToolsBuildItem.class, DebugInfoBuildItem.class),
        List.of()
    ));

@Test 
public void testProductionMode() {
    // Test context will be passed to build step
    // Build step will produce ProductionOptimizationBuildItem
}

Build Step with Resource Processing

public class ResourceProcessingBuildStep extends ProdModeTestBuildStep {
    
    public ResourceProcessingBuildStep(Map<String, Object> testContext) {
        super(testContext);
    }
    
    @Override
    public void execute(BuildContext context) {
        ApplicationArchivesBuildItem archives = context.consume(ApplicationArchivesBuildItem.class);
        
        for (ApplicationArchive archive : archives.getAllApplicationArchives()) {
            // Process resources in the archive
            Path resourcesPath = archive.getArchiveRoot().resolve("META-INF/resources");
            
            if (Files.exists(resourcesPath)) {
                try {
                    Files.walk(resourcesPath)
                        .filter(Files::isRegularFile)
                        .forEach(this::processResource);
                } catch (IOException e) {
                    throw new RuntimeException("Failed to process resources", e);
                }
            }
        }
        
        // Produce build item indicating resources were processed
        context.produce(new ProcessedResourcesBuildItem());
    }
    
    private void processResource(Path resourcePath) {
        // Custom resource processing logic
        Map<String, Object> testContext = getTestContext();
        String processingMode = (String) testContext.get("resourceProcessingMode");
        
        // Process based on mode
        if ("minify".equals(processingMode)) {
            // Minify resource
        } else if ("optimize".equals(processingMode)) {
            // Optimize resource
        }
    }
}

Testing Build Step Execution

@Test
public void testBuildStepExecution() {
    // Test that custom build steps were executed correctly
    // This can be verified through:
    
    // 1. Checking build logs
    String buildOutput = config.getStartupConsoleOutput();
    assertTrue(buildOutput.contains("MyCustomBuildStep executed"));
    
    // 2. Examining build artifacts (if accessible)
    // 3. Testing runtime behavior that depends on build step output
    // 4. Using test context to pass verification flags
}

@Test 
public void testBuildStepWithContext() {
    // Test context is automatically passed to build steps
    // You can verify build step behavior by examining the results
    
    // The build step can access test context and modify behavior accordingly
    // This allows for parameterized build testing
}

Build Item Examples

// Simple build item
public class MyBuildItem extends BuildItem {
    private final String data;
    
    public MyBuildItem(String data) {
        this.data = data;
    }
    
    public String getData() {
        return data;
    }
}

// Complex build item with multiple properties
public class ConfigurationBuildItem extends BuildItem {
    private final Map<String, String> properties;
    private final List<String> features;
    
    public ConfigurationBuildItem(Map<String, String> properties, List<String> features) {
        this.properties = Collections.unmodifiableMap(properties);
        this.features = Collections.unmodifiableList(features);
    }
    
    public Map<String, String> getProperties() {
        return properties;
    }
    
    public List<String> getFeatures() {
        return features;
    }
}

Best Practices

Build Step Design

  1. Keep build steps focused: Each build step should have a single responsibility
  2. Use test context effectively: Pass configuration and test data through test context
  3. Handle dependencies correctly: Declare all consumed and produced build items
  4. Error handling: Provide clear error messages for build failures

Testing Build Chains

  1. Verify build item production: Ensure build steps produce expected build items
  2. Test build item consumption: Verify that build steps properly consume dependencies
  3. Integration testing: Test complete build chains with multiple steps
  4. Performance testing: Measure build step execution time for complex operations

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkus--quarkus-junit5-internal

docs

build-chain-customization.md

dev-mode-testing.md

index.md

logging-test-resources.md

prod-mode-testing.md

results-utilities.md

unit-testing.md

tile.json