The @DefaultLocale and @DefaultTimeZone annotations can be used to change the values returned from Locale.getDefault() and TimeZone.getDefault(), respectively, which are often used implicitly when no specific locale or time zone is chosen. Both annotations work on the test class level and on the test method level, and are inherited from higher-level containers. After the annotated element has been executed, the initial default value is restored.

@DefaultLocale

The default Locale can be specified using an IETF BCP 47 language tag string

@Test
@DefaultLocale("zh-Hant-TW")
void test_with_language() {
    assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW"));
}

Alternatively the default Locale can be created using the following attributes of which a Locale Builder can create an instance with:

  • language or

  • language and country or

  • language, country, and variant

Note
The variant needs to be a string which follows the IETF BCP 47 / RFC 5646 syntax!
@Test
@DefaultLocale(language = "en")
void test_with_language_only() {
    assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build());
}

@Test
@DefaultLocale(language = "en", country = "EN")
void test_with_language_and_country() {
    assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").setRegion("EN").build());
}

@Test
@DefaultLocale(language = "ja", country = "JP", variant = "japanese")
void test_with_language_and_country_and_vairant() {
    assertThat(Locale.getDefault())
            .isEqualTo(new Locale.Builder().setLanguage("ja").setRegion("JP").setVariant("japanese").build());
}

Note that mixing language tag configuration and constructor based configuration will cause an ExtensionConfigurationException to be thrown. Furthermore, a variant can only be specified if country is also specified. If variant is specified without country, an ExtensionConfigurationException will be thrown.

Any method level @DefaultLocale configurations will override class level configurations.

@DefaultLocale(language = "fr")
class MyLocaleTests {

    @Test
    void test_with_class_level_configuration() {
        assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("fr").build());
    }

    @Test
    @DefaultLocale(language = "en")
    void test_with_method_level_configuration() {
        assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build());
    }

}
Note
A class-level configuration means that the specified locale is set before and reset after each individual test in the annotated class.

@DefaultTimeZone

The default TimeZone is specified according to the TimeZone.getTimeZone(String) method.

@Test
@DefaultTimeZone("CET")
void test_with_short_zone_id() {
    assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"));
}

@Test
@DefaultTimeZone("Africa/Juba")
void test_with_long_zone_id() {
    assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"));
}

Any method level @DefaultTimeZone configurations will override class level configurations:

@DefaultTimeZone("CET")
class MyTimeZoneTests {

    @Test
    void test_with_class_level_configuration() {
        assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"));
    }

    @Test
    @DefaultTimeZone("Africa/Juba")
    void test_with_method_level_configuration() {
        assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"));
    }

}
Note
A class-level configuration means that the specified time zone is set before and reset after each individual test in the annotated class.

Thread-Safety

Since default locale and time zone are global state, reading and writing them during parallel test execution can lead to unpredictable results and flaky tests. The @DefaultLocale and @DefaultTimeZone extensions are prepared for that and tests annotated with them will never execute in parallel (thanks to resource locks) to guarantee correct test results.

However, this does not cover all possible cases. Tested code that reads or writes default locale and time zone independently of the extensions can still run in parallel to them and may thus behave erratically when, for example, it unexpectedly reads a locale set by the extension in another thread. Tests that cover code that reads or writes the default locale or time zone need to be annotated with the respective annotation:

  • @ReadsDefaultLocale

  • @ReadsDefaultTimeZone

  • @WritesDefaultLocale

  • @WritesDefaultTimeZone

Tests annotated in this way will never execute in parallel with tests annotated with @DefaultLocale or @DefaultTimeZone.