Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a TestResourcesScope annotation #241

Merged
merged 1 commit into from
May 4, 2023
Merged

Conversation

melix
Copy link
Collaborator

@melix melix commented May 4, 2023

By default, test resources are shared between all tests. This means, in particular, that a single container will be spawned if multiple tests require the same property. For example, we would have a single MySQL container for the whole test suite, which makes testing significantly faster, avoiding containers to be started and shutdown in each test.

However, in some situations it may be required to isolate tests from each other, and a test may want to work with its own, isolated container.

This commit introduces a new TestResourcesScope annotation which can be put added at a test class level, which defines the scope of the test resources used in that test. A scope can be shared by multiple tests, and you can see the general case (all tests share the same resources) as the "root scope". A scope is closed whenever the last test which works in that scope is finished.

Before, doing this required assigning a test property to the test and manually counting the number of tests which are executed, then calling the test resources client directly when all tests are executed, which is both error prone and imprecise (in case only a subset of the tests are executed).

Because of implementation limitations, the way the scope is recognized is by using a thread local, which has a couple consequences:

  1. it only works properly if tests are executed in a single JVM.
  2. tests which use custom threads and execute the application in that thread wouldn't see the scope and have to rely on using the client

This significantly simplifies the test setup in Micronaut Data. Take this existing code:

class MySqlJoinSpec extends AbstractJoinSpec implements MySQLTestPropertyProvider {
...
}

The MySQLTestPropertyProvider is implemented as:

trait MySQLTestPropertyProvider implements SharedTestResourcesDatabaseTestPropertyProvider {

    @Override
    Dialect dialect() {
        Dialect.MYSQL
    }

    @Override
    int sharedSpecsCount() {
        return 7
    }
}

And SharedTestResourcesDatabaseTestPropertyProvider:

trait SharedTestResourcesDatabaseTestPropertyProvider implements TestResourcesDatabaseTestPropertyProvider {

    abstract int sharedSpecsCount()

    def cleanupSpec() {
        int testsCompleted = DbHolder.DB_TYPE_TEST_COUNT.compute(dbType(), (dbType, val) -> val ? val + 1 : 1)
        if (testsCompleted == sharedSpecsCount()) {
            try {
                TestResourcesClient testResourcesClient = TestResourcesClientFactory.fromSystemProperties().get()
                testResourcesClient.closeScope(dbType())
            } catch (Exception e) {
                // Ignore
            }
        }
    }
}

And finally TestResourcesDatabaseTestPropertyProvider:

trait TestResourcesDatabaseTestPropertyProvider implements TestPropertyProvider {

   ...

    Map<String, String> getDataSourceProperties(String dataSourceName) {
        def prefix = 'datasources.' + dataSourceName
        return [
                'micronaut.test.resources.scope': dbType(),
                (prefix + '.db-type')           : dbType(),
                (prefix + '.schema-generate')   : schemaGenerate(),
                (prefix + '.dialect')           : dialect(),
                (prefix + '.packages')          : packages(),
        ] as Map<String, String>
    }

}

With this PR, we can simplify the code a lot, by getting rid of SharedTestResourcesDatabaseTestPropertyProvider altogether, and annotating TestResourcesDatabaseTestPropertyProvider:

@TestResourcesScope(namingStrategy = ScopeNamingStrategy.PackageName)
trait TestResourcesDatabaseTestPropertyProvider implements TestPropertyProvider {
    ...
    Map<String, String> getDataSourceProperties(String dataSourceName) {
        def prefix = 'datasources.' + dataSourceName
        return [
                (prefix + '.db-type')           : dbType(),
                (prefix + '.schema-generate')   : schemaGenerate(),
                (prefix + '.dialect')           : dialect(),
                (prefix + '.packages')          : packages(),
        ] as Map<String, String>
    }

}

It is no longer required to explicitly call the client, nor to set the scope in the test property provider, and the scope can be automatically derived from the package of the test class.

@graemerocher if this is approved I will create a new PR documenting this feature and the one it depends on (#231)

By default, test resources are shared between all tests. This means,
in particular, that a single container will be spawned if multiple
tests require the same property. For example, we would have a single
MySQL container for the whole test suite, which makes testing
significantly faster, avoiding containers to be started and shutdown
in each test.

However, in some situations it may be required to isolate tests
from each other, and a test may want to work with its own, isolated
container.

This commit introduces a new `TestResourcesScope` annotation which
can be put added at a _test class_ level, which defines the scope
of the test resources used in that test. A scope can be shared
by multiple tests, and you can see the general case (all tests
share the same resources) as the "root scope". A scope is closed
whenever the last test which works in that scope is finished.

Before, doing this required assigning a test property to the
test and manually counting the number of tests which are executed,
then calling the test resources client directly when all tests
are executed, which is both error prone and imprecise (in case
only a subset of the tests are executed).

Because of implementation limitations, the way the scope is
recognized is by using a thread local, which has a couple
consequences:

1. it only works properly if tests are executed in a _single_
JVM.
2. tests which use custom threads and execute the application
in that thread wouldn't see the scope and have to rely on
using the client
@melix melix added the type: improvement A minor improvement to an existing feature label May 4, 2023
@melix melix added this to the 2.0.0-M3 milestone May 4, 2023
@melix melix requested a review from graemerocher May 4, 2023 09:53
@melix melix self-assigned this May 4, 2023
@sonarcloud
Copy link

sonarcloud bot commented May 4, 2023

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 13 Code Smells

3.9% 3.9% Coverage
0.0% 0.0% Duplication

@melix melix merged commit fe3d1fc into master May 4, 2023
@melix melix deleted the cc/extension-junit-platform branch May 4, 2023 12:21
@melix melix modified the milestones: 2.0.0-M3, 2.0.0-M4 May 4, 2023
melix added a commit that referenced this pull request May 4, 2023
This commit adds user documentation for the new Micronaut Test
extensions available in test resources.

This is a follow up to #231 and #241
@melix melix modified the milestones: 2.0.0-M4, 2.0.0-M3 May 4, 2023
melix added a commit that referenced this pull request May 4, 2023
* Documentation for Micronaut test extensions

This commit adds user documentation for the new Micronaut Test
extensions available in test resources.

This is a follow up to #231 and #241

* Address review comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: improvement A minor improvement to an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants