Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package android.testing;
     16 
     17 import static org.junit.Assert.assertNotNull;
     18 
     19 import android.annotation.Nullable;
     20 import android.app.Fragment;
     21 import android.app.FragmentController;
     22 import android.app.FragmentHostCallback;
     23 import android.app.FragmentManagerNonConfig;
     24 import android.content.Context;
     25 import android.graphics.PixelFormat;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Parcelable;
     29 import android.view.LayoutInflater;
     30 import android.view.View;
     31 import android.view.WindowManager;
     32 import android.view.WindowManager.LayoutParams;
     33 import android.widget.FrameLayout;
     34 
     35 import androidx.test.InstrumentationRegistry;
     36 
     37 import org.junit.After;
     38 import org.junit.Before;
     39 import org.junit.Rule;
     40 import org.junit.Test;
     41 
     42 import java.io.FileDescriptor;
     43 import java.io.PrintWriter;
     44 
     45 /**
     46  * Base class for fragment class tests.  Just adding one for any fragment will push it through
     47  * general lifecycle events and ensure no basic leaks are happening.  This class also implements
     48  * the host for subclasses, so they can push it into desired states and do any unit testing
     49  * required.
     50  */
     51 public abstract class BaseFragmentTest {
     52 
     53     private static final int VIEW_ID = 42;
     54     private final Class<? extends Fragment> mCls;
     55     protected Handler mHandler;
     56     protected FrameLayout mView;
     57     protected FragmentController mFragments;
     58     protected Fragment mFragment;
     59 
     60     @Rule
     61     public final TestableContext mContext = getContext();
     62 
     63     public BaseFragmentTest(Class<? extends Fragment> cls) {
     64         mCls = cls;
     65     }
     66 
     67     protected void createRootView() {
     68         mView = new FrameLayout(mContext);
     69     }
     70 
     71     @Before
     72     public void setupFragment() throws Exception {
     73         createRootView();
     74         mView.setId(VIEW_ID);
     75 
     76         assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper",
     77                 TestableLooper.get(this));
     78         TestableLooper.get(this).runWithLooper(() -> {
     79             mHandler = new Handler();
     80 
     81             mFragment = instantiate(mContext, mCls.getName(), null);
     82             mFragments = FragmentController.createController(new HostCallbacks());
     83             mFragments.attachHost(null);
     84             mFragments.getFragmentManager().beginTransaction()
     85                     .replace(VIEW_ID, mFragment)
     86                     .commit();
     87         });
     88     }
     89 
     90     /**
     91      * Allows tests to sub-class TestableContext if they want to provide any extended functionality
     92      * or provide a {@link LeakCheck} to the TestableContext upon instantiation.
     93      */
     94     protected TestableContext getContext() {
     95         return new TestableContext(InstrumentationRegistry.getContext());
     96     }
     97 
     98     @After
     99     public void tearDown() throws Exception {
    100         if (mFragments != null) {
    101             // Set mFragments to null to let it know not to destroy.
    102             TestableLooper.get(this).runWithLooper(() -> mFragments.dispatchDestroy());
    103         }
    104     }
    105 
    106     @Test
    107     public void testCreateDestroy() {
    108         mFragments.dispatchCreate();
    109         processAllMessages();
    110         destroyFragments();
    111     }
    112 
    113     @Test
    114     public void testStartStop() {
    115         mFragments.dispatchStart();
    116         processAllMessages();
    117         mFragments.dispatchStop();
    118         processAllMessages();
    119     }
    120 
    121     @Test
    122     public void testResumePause() {
    123         mFragments.dispatchResume();
    124         processAllMessages();
    125         mFragments.dispatchPause();
    126         processAllMessages();
    127     }
    128 
    129     @Test
    130     public void testAttachDetach() {
    131         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    132                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
    133                 LayoutParams.TYPE_SYSTEM_ALERT,
    134                 0, PixelFormat.TRANSLUCENT);
    135         mFragments.dispatchResume();
    136         processAllMessages();
    137         attachFragmentToWindow();
    138         detachFragmentToWindow();
    139         mFragments.dispatchPause();
    140         processAllMessages();
    141     }
    142 
    143     @Test
    144     public void testRecreate() {
    145         mFragments.dispatchResume();
    146         processAllMessages();
    147         recreateFragment();
    148         processAllMessages();
    149     }
    150 
    151     @Test
    152     public void testMultipleResumes() {
    153         mFragments.dispatchResume();
    154         processAllMessages();
    155         mFragments.dispatchStop();
    156         processAllMessages();
    157         mFragments.dispatchResume();
    158         processAllMessages();
    159     }
    160 
    161     protected void recreateFragment() {
    162         mFragments.dispatchPause();
    163         Parcelable p = mFragments.saveAllState();
    164         mFragments.dispatchDestroy();
    165 
    166         mFragments = FragmentController.createController(new HostCallbacks());
    167         mFragments.attachHost(null);
    168         mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
    169         mFragments.dispatchResume();
    170         mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID);
    171     }
    172 
    173     protected void attachFragmentToWindow() {
    174         ViewUtils.attachView(mView);
    175         TestableLooper.get(this).processAllMessages();
    176     }
    177 
    178     protected void detachFragmentToWindow() {
    179         ViewUtils.detachView(mView);
    180         TestableLooper.get(this).processAllMessages();
    181     }
    182 
    183     protected void destroyFragments() {
    184         mFragments.dispatchDestroy();
    185         processAllMessages();
    186         mFragments = null;
    187     }
    188 
    189     protected void processAllMessages() {
    190         TestableLooper.get(this).processAllMessages();
    191     }
    192 
    193     /**
    194      * Method available for override to replace fragment instantiation.
    195      */
    196     protected Fragment instantiate(Context context, String className, @Nullable Bundle arguments) {
    197         return Fragment.instantiate(context, className, arguments);
    198     }
    199 
    200     private View findViewById(int id) {
    201         return mView.findViewById(id);
    202     }
    203 
    204     private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> {
    205         public HostCallbacks() {
    206             super(mContext, BaseFragmentTest.this.mHandler, 0);
    207         }
    208 
    209         @Override
    210         public BaseFragmentTest onGetHost() {
    211             return BaseFragmentTest.this;
    212         }
    213 
    214         @Override
    215         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    216         }
    217 
    218         @Override
    219         public Fragment instantiate(Context context, String className, Bundle arguments) {
    220             return BaseFragmentTest.this.instantiate(context, className, arguments);
    221         }
    222 
    223         @Override
    224         public boolean onShouldSaveFragmentState(Fragment fragment) {
    225             return true; // True for now.
    226         }
    227 
    228         @Override
    229         public LayoutInflater onGetLayoutInflater() {
    230             return LayoutInflater.from(mContext);
    231         }
    232 
    233         @Override
    234         public boolean onUseFragmentManagerInflaterFactory() {
    235             return true;
    236         }
    237 
    238         @Override
    239         public boolean onHasWindowAnimations() {
    240             return false;
    241         }
    242 
    243         @Override
    244         public int onGetWindowAnimations() {
    245             return 0;
    246         }
    247 
    248         @Override
    249         public void onAttachFragment(Fragment fragment) {
    250         }
    251 
    252         @Nullable
    253         @Override
    254         public View onFindViewById(int id) {
    255             return BaseFragmentTest.this.findViewById(id);
    256         }
    257 
    258         @Override
    259         public boolean onHasView() {
    260             return true;
    261         }
    262     }
    263 }
    264