Change locale while executing Espresso test
Asked Answered
V

2

10

I am creating a simple layout that should support arabic language and RTL layout. Everything works fine. Now I want to write an Espresso test and assert weather it's actually showing the translated text or not. e.g. For arabic language it should display text from arabic strings.xml.

So far I tried below code as a TestRule.

public void setLocale(Locale locale) {
        Resources resources = InstrumentationRegistry.getTargetContext().getResources();
        Locale.setDefault(locale);
        Configuration config = resources.getConfiguration();
        config.locale = locale;
        resources.updateConfiguration(config, resources.getDisplayMetrics());
    }

The above code changes the layout direction but doesn't load resources from the localised directory.

I am not doing anything extra but something like http://www.andreamaglie.com/2016/a-test-rule-for-setting-device-locale/

Am I missing anything?

Vulgarism answered 21/11, 2018 at 16:55 Comment(2)
How do you construct the locale?Divestiture
@Divestiture like this Locale("ar", "AE")Vulgarism
N
7

I've create a small test project using the link you provided for US and UK, the main classes are further down the answer, but it's a public project so you can just download it.
For 'AE' you will need to create a strings.xml under values-ar-rAE (see this link).
Edit: added another test for each language and MyActions class.
Credits: MyActions is from here, tests examples are from here, you might need to stop animation from the developer settings (from second link and here)

ForceLocaleRule:

import android.content.res.Configuration;
import android.content.res.Resources;
import android.support.test.InstrumentationRegistry;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.util.Locale;

public class ForceLocaleRule implements TestRule {

    private final Locale testLocale;
    private Locale deviceLocale;

    public ForceLocaleRule(Locale testLocale) {
        this.testLocale = testLocale;
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            public void evaluate() throws Throwable {
                try {
                    if (testLocale != null) {
                        deviceLocale = Locale.getDefault();
                        setLocale(testLocale);
                    }

                    base.evaluate();
                } finally {
                    if (deviceLocale != null) {
                        setLocale(deviceLocale);
                    }
                }
            }
        };
    }

    public void setLocale(Locale locale) {
        Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources();
        Locale.setDefault(locale);
        Configuration config = resources.getConfiguration();
        config.locale = locale;
        resources.updateConfiguration(config, resources.getDisplayMetrics());
    }

}

US test:

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Locale;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static example.com.testlocale.MyActions.setTextInTextView;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;

@RunWith(AndroidJUnit4.class)
public class MainActivityUsTest {

    @ClassRule
    public static final ForceLocaleRule localeTestRule = new ForceLocaleRule(Locale.US);

    @Rule
    public ActivityTestRule<MainActivity> myActivityRule = new ActivityTestRule<>(MainActivity.class);

    private Context context;

    @Before
    public void setUp() {
        context = InstrumentationRegistry.getTargetContext();
    }

    @Test
    public void testAirplaneEn() {
        assertEquals("airplane", context.getString(R.string.airplane));
    }

    @Test
    public void testAirplaneEnOnView() {
        onView(withId(R.id.text_view))
                .perform(setTextInTextView(context.getString(R.string.airplane)),
                        closeSoftKeyboard());
        onView(withId(R.id.text_view))
                .check(matches(withText(containsString("airplane"))));
    }

}

UK test:

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Locale;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static example.com.testlocale.MyActions.setTextInTextView;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;

@RunWith(AndroidJUnit4.class)
public class MainActivityGbTest {

    @ClassRule
    public static final ForceLocaleRule localeTestRule = new ForceLocaleRule(Locale.UK);

    @Rule
    public ActivityTestRule<MainActivity> myActivityRule = new ActivityTestRule<>(MainActivity.class);

    private Context context;

    @Before
    public void setUp() {
        context = InstrumentationRegistry.getTargetContext();
    }

    @Test
    public void testAirplaneEnGB() {
        assertEquals("aeroplane", context.getString(R.string.airplane));
    }

    @Test
    public void testAirplaneEnOnView() {
        onView(withId(R.id.text_view))
                .perform(setTextInTextView(context.getString(R.string.airplane)),
                        closeSoftKeyboard());
        onView(withId(R.id.text_view))
                .check(matches(withText(containsString("aeroplane"))));
    }

}

MyActions:

import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.view.View;
import android.widget.TextView;

import org.hamcrest.Matcher;

import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static org.hamcrest.CoreMatchers.allOf;

public class MyActions {

    public static ViewAction setTextInTextView(final String value){
        return new ViewAction() {
            @SuppressWarnings("unchecked")
            @Override
            public Matcher<View> getConstraints() {
                return allOf(isDisplayed(), isAssignableFrom(TextView.class));
            }

            @Override
            public void perform(UiController uiController, View view) {
                ((TextView) view).setText(value);
            }

            @Override
            public String getDescription() {
                return "set text to TextView";
            }
        };
    }

}

values-en-rUS\strings.xml

<resources>
    <string name="app_name">TestLocale</string>
    <string name="airplane">airplane</string>
</resources>

values-en-rGB\strings.xml

<resources>
    <string name="app_name">TestLocale</string>
    <string name="airplane">aeroplane</string>
</resources>
Nf answered 16/12, 2018 at 14:38 Comment(9)
Thanks for checking this. Everything looks fine. But does this work same when I do assertion on view which shows value from strings.xml. e.g. Let's say there is a fragment/activity which displays @string/airplane on some TextView. If I do view assertion using espresso does it fetch correct locale through this force locale?Vulgarism
@HardikTrivedi I've edited my answer with more tests, it does not seem to load the text originally, but once you set the text it is correct, hope it's what you are looking forNf
This approach was perfect until Android level 24, but from Android 25 and later on it's not feasible anymore unfortunately. Here's a very well laid out article about different locale handling among Android levels: proandroiddev.com/…Bittner
@NicolaBeghin you might want to add an answer so it is documented in SO.Nf
I dont understand, its not working for me ... is it working for you guys ??Pageantry
@TaiNguyen if you are working with androidx you might want to look at this answer (I have not tested it).Nf
@Nf thank you for your answer but I dont understand how It can works... I mean I dont know where to set the language with the method under yoursPageantry
@TaiNguyen he references another answer. I suggest you look there, or add a comment in that answer for clarification.Nf
Does anybody know how this works with SDK v31?Industrials
B
0

For anyone experiencing issues with the Locale change after switching to androidx: androidx.appcompat:appcompat:1.1.0 is currently bugged, but it can be worked around by downgrading to 1.0.2 or by adding @0101100101's solution in your AppCompatActivity. Just tested in Espresso unit tests and all is good.

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (overrideConfiguration != null) {
        int uiMode = overrideConfiguration.uiMode;
        overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
        overrideConfiguration.uiMode = uiMode;
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}

ref. https://mcmap.net/q/65858/-change-locale-not-work-after-migrate-to-androidx

Bittner answered 4/11, 2019 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.