17.3.1Best Practices for Testing with SmileCdrContainer and SmileHarness

 

This document provides recommendations for effective testing with SmileCdrContainer and SmileHarness. Following these best practices will help you write more efficient, reliable, and maintainable tests.

17.3.1.1Container Management

17.3.1.1.1Reuse Containers When Possible

Starting and stopping Docker containers is resource-intensive. When writing multiple tests that use the same container configuration, consider using a shared container instance:

@Testcontainers
class MyTestSuite {
	
    // Shared container for all tests in this class
    @Container
    private static final SmileCdrContainer container = new SmileCdrContainer("2025.02.R02");
    
    private static SmileHarness harness;
    
    @BeforeAll
    static void setup() {
        harness = container.getHarness();
    }
    
    @Test
    void test1() {
        // Use the shared container and harness
    }
    
    @Test
    void test2() {
        // Use the same shared container and harness
    }
}

Note that since the SmileCdrContainer is defined as static, it will be reused through the entire test class. Important to note that any data stored by one test will be accessible by another. This may not be what you want, so it is important to consider which lifecycle to use.

17.3.1.1.2Use Appropriate Container Lifecycle

Consider the appropriate lifecycle for your container:

  • Per Class: Use @Container on a static field to share a container across all tests in a class.
  • Per Test: Use @Container on a non-static field to create a fresh isolated container for each individual test.

Choose based on whether your tests need isolation or can share state.

17.3.1.1.3Set Appropriate Timeouts

Docker containers can take time to start, especially for complex applications like Smile CDR. Set appropriate timeouts to avoid test failures due to slow startup:

SmileCdrContainer container = new SmileCdrContainer("2025.02.R02")
    .withStartupTimeout(Duration.ofMinutes(5));

17.3.1.2Test Design

17.3.1.2.1Write Focused Tests

Each test should focus on testing a specific functionality:

@Test
void testPatientCreation() {
    // Test only patient creation
}

@Test
void testPatientSearch() {
    // Test only patient search
}

Avoid writing tests that try to test too many things at once, as they become difficult to maintain and debug.

17.3.1.2.2Use Descriptive Test Names

Use descriptive names for your tests that clearly indicate what is being tested:

@Test
void testCreatePatientWithMultipleIdentifiers() {
    // Test creating a patient with multiple identifiers
}

17.3.1.2.3Use Test Data Helpers

Create helper methods or classes for generating test data to make your tests more readable and maintainable:

private Patient createTestPatient() {
    Patient patient = new Patient();
    patient.addName().setFamily("Test").addGiven("Patient");
    patient.setGender(Enumerations.AdministrativeGender.MALE);
    return patient;
}

17.3.1.3Client Usage

17.3.1.3.1Reuse Clients

Create clients once and reuse them throughout your tests to avoid unnecessary overhead:

private static IGenericClient fhirClient;
private static AdminJsonRestClient adminClient;

@BeforeAll
static void setup() {
    harness = container.getHarness();
    fhirClient = harness.getSuperuserFhirClient();
    adminClient = harness.getAdminJsonClient();
}

17.3.1.3.2Use Appropriate Authentication

Use the appropriate level of authentication for your tests:

  • Use getSuperuserFhirClient() for administrative tasks
  • Use getFhirClient() with custom authentication for testing specific user scenarios

17.3.1.4Configuration Management

17.3.1.4.1Use Custom Properties Files

For tests that require specific configurations, use custom properties files:

SmileCdrContainer container = new SmileCdrContainer()
    .withPropertiesFile("custom-config.properties");

This allows you to tailor the Smile CDR configuration to your specific test needs.

17.3.1.5Performance Considerations

17.3.1.5.1Minimize Container Restarts

Starting and stopping containers is expensive. Design your tests to minimize container restarts:

  • Group related tests in the same class to share a container
  • Use @TestMethodOrder to control test execution order when necessary

17.3.1.5.2Use Parallel Test Execution Carefully

Testcontainers supports parallel test execution, but be careful with resource usage:

  • Each container requires significant resources
  • Too many containers running in parallel can slow down your tests or cause resource exhaustion

17.3.1.5.3Consider Resource Requirements

Smile CDR can be resource-intensive. Make sure your test environment has sufficient resources:

  • Allocate enough memory to Docker
  • Ensure enough CPU cores are available
  • Monitor resource usage during test execution

17.3.1.6Debugging

17.3.1.6.1Inspect Container State

Use the Testcontainers API to inspect the container state:

// Get container logs
String logs = container.getLogs();

// Execute a command in the container
ExecResult result = container.execInContainer("ls", "-la");

17.3.1.7Advanced Techniques

17.3.1.7.1Custom Wait Strategies

By default, SmileCdrContainer is considered started when the "You are up and running" log message arrives in the logs. However, you can use custom wait strategies to ensure the container is fully ready before tests, if you have some alternate start conditions.

SmileCdrContainer container = new SmileCdrContainer("2025.02.R02")
    .waitingFor(Wait.forHttp("/Patient")
        .forPort(8000)
        .withBasicCredentials("admin", "password")
        .forStatusCode(200));

17.3.1.8Conclusion

By following these best practices, you can write more effective tests with SmileCdrContainer and SmileHarness. Remember that good tests are:

  • Focused: Each test should test one thing
  • Fast: Tests should run quickly to provide rapid feedback
  • Reliable: Tests should produce consistent results
  • Maintainable: Tests should be easy to understand and modify
  • Isolated: Tests should not depend on or affect other tests