Test Templates

Getting Started

The test template function helps extract some common test and infrastructure code to reusable components. In particular, it is useful for testing class hierarchies.

To create a test template, place the @TestTemplate macro above the abstract class.

@TestTemplate
abstract class DbTest {
    public prop dbConnection: DbConnection

    @TestCase
    func testCommonDbApi1() { /* ... */}

    @TestCase
    func testCommonDbApi2() { /* ... */}
}

You can use this template to create multiple actual test suites, such as testing connections to different specific databases. To use the test template, you only need to inherit the corresponding class.

@Test
class MySqlTest <: DbTest {
    var dbConnection_: ?DbConnection = None

    override prop dbConnection: DbConnection {
        get() {
            dbConnection_.getOrThrow()
        }
    }

    @BeforeAll
    func initializeConnection() {
        dbConnection_ = Some(...)
    }

    @AfterAll
    func closeConnection() {
        dbConnection_.close()
    }

    @TestCase
    func testSpecificlyMySqlFeatures() {
        /* ... */
    }
}

Each test case will run as if it were written in the actual test class. The result is as follows:

------------------------------------------------------------
TP: default, time elapsed: 177679 ns, RESULT:
    TCS: MySqlTest, time elapsed: 157163 ns, RESULT:
    [ PASSED ] CASE: testCommonDbApi1 (34704 ns)
    [ PASSED ] CASE: testCommonDbApi2 (8480 ns)
    [ PASSED ] CASE: testSpecificlyMySqlFeatures (8329 ns)
Summary: TOTAL: 3
    PASSED: 3, SKIPPED: 0, ERROR: 0
    FAILED: 0
------------------------------------------------------------

The test template can be built using other test templates.

Lifecycle Methods

The test template can also contain some lifecycle methods, such as @BeforeAll, @AfterAll, @BeforeEach, and @AfterEach. Lifecycle methods are executed in the specified sequence:

  • The @Before_ lifecycle method is executed in the sequence from the base class to the inheritance class.
  • The @After_ lifecycle method is executed in the sequence from the inheritance class to the base class.

The @_Each derived methods are also applicable to the test cases of the base class.

@TestTemplate
abstract class BaseTemplate {
    @BeforeEach
    func baseBeforeEach() { println("base before each") }

    @AfterEach
    func baseAfterEach() { println("base after each") }
}

@TestTemplate
abstract class Template <: BaseTemplate {
    @TestCase
    func templateCase() { println("template case") }
}

@Test
class Test <: Template {
    @BeforeEach
    func beforeEach() { println("before each") }

    @AfterEach
    func afterEach() { println("after each") }

    @TestCase
    func testCase() { println("case") }
}

The output is as follows (when output capture is enabled):

------------------------------------------------------------
TP: default, time elapsed: 456925 ns, RESULT:
    TCS: Test, time elapsed: 456925 ns, RESULT:
    [ PASSED ] CASE: templateCase (38228 ns)
    STDOUT:
    base before each
    before each
    template case
    after each
    base after each
    [ PASSED ] CASE: testCase (16098 ns)
    STDOUT:
    base before each
    before each
    case
    after each
    base after each
Summary: TOTAL: 2
    PASSED: 2, SKIPPED: 0, ERROR: 0
    FAILED: 0
------------------------------------------------------------

Configuration

@Configure can be placed above the test template class, but @Configure of the inheritance class overwrites the value placed for the base class. All test cases are executed under the combined configuration.

Rules for Feature interactions

  • @Parallel cannot be used together with @TestTemplate.
  • @Types cannot be used together with @TestTemplate.
  • @Bench can be used in templates and executed when --bench is specified as if the benchmark is placed in the inheritance class.