Home | History | Annotate | Download | only in lifecycle
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.lifecycle;
     18 
     19 import static org.hamcrest.CoreMatchers.is;
     20 import static org.hamcrest.MatcherAssert.assertThat;
     21 import static org.mockito.Matchers.any;
     22 import static org.mockito.Mockito.mock;
     23 import static org.mockito.Mockito.never;
     24 import static org.mockito.Mockito.reset;
     25 import static org.mockito.Mockito.verify;
     26 import static org.mockito.Mockito.when;
     27 
     28 import androidx.annotation.Nullable;
     29 import androidx.arch.core.executor.ArchTaskExecutor;
     30 import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
     31 import androidx.lifecycle.util.InstantTaskExecutor;
     32 
     33 import org.junit.Before;
     34 import org.junit.Rule;
     35 import org.junit.Test;
     36 import org.junit.runner.RunWith;
     37 import org.junit.runners.JUnit4;
     38 
     39 @SuppressWarnings("unchecked")
     40 @RunWith(JUnit4.class)
     41 public class MediatorLiveDataTest {
     42 
     43     @Rule
     44     public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
     45 
     46     private LifecycleOwner mOwner;
     47     private LifecycleRegistry mRegistry;
     48     private MediatorLiveData<String> mMediator;
     49     private LiveData<String> mSource;
     50     private boolean mSourceActive;
     51 
     52     @Before
     53     public void setup() {
     54         mOwner = mock(LifecycleOwner.class);
     55         mRegistry = new LifecycleRegistry(mOwner);
     56         when(mOwner.getLifecycle()).thenReturn(mRegistry);
     57         mMediator = new MediatorLiveData<>();
     58         mSource = new LiveData<String>() {
     59             @Override
     60             protected void onActive() {
     61                 mSourceActive = true;
     62             }
     63 
     64             @Override
     65             protected void onInactive() {
     66                 mSourceActive = false;
     67             }
     68         };
     69         mSourceActive = false;
     70         mMediator.observe(mOwner, mock(Observer.class));
     71         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
     72         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
     73     }
     74 
     75     @Before
     76     public void swapExecutorDelegate() {
     77         ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     78     }
     79 
     80     @Test
     81     public void testSingleDelivery() {
     82         Observer observer = mock(Observer.class);
     83         mMediator.addSource(mSource, observer);
     84         mSource.setValue("flatfoot");
     85         verify(observer).onChanged("flatfoot");
     86         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
     87         reset(observer);
     88         verify(observer, never()).onChanged(any());
     89     }
     90 
     91     @Test
     92     public void testChangeWhileInactive() {
     93         Observer observer = mock(Observer.class);
     94         mMediator.addSource(mSource, observer);
     95         mMediator.observe(mOwner, mock(Observer.class));
     96         mSource.setValue("one");
     97         verify(observer).onChanged("one");
     98         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
     99         reset(observer);
    100         mSource.setValue("flatfoot");
    101         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    102         verify(observer).onChanged("flatfoot");
    103     }
    104 
    105 
    106     @Test
    107     public void testAddSourceToActive() {
    108         mSource.setValue("flatfoot");
    109         Observer observer = mock(Observer.class);
    110         mMediator.addSource(mSource, observer);
    111         verify(observer).onChanged("flatfoot");
    112     }
    113 
    114     @Test
    115     public void testAddSourceToInActive() {
    116         mSource.setValue("flatfoot");
    117         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    118         Observer observer = mock(Observer.class);
    119         mMediator.addSource(mSource, observer);
    120         verify(observer, never()).onChanged(any());
    121         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    122         verify(observer).onChanged("flatfoot");
    123     }
    124 
    125     @Test
    126     public void testRemoveSource() {
    127         mSource.setValue("flatfoot");
    128         Observer observer = mock(Observer.class);
    129         mMediator.addSource(mSource, observer);
    130         verify(observer).onChanged("flatfoot");
    131         mMediator.removeSource(mSource);
    132         reset(observer);
    133         mSource.setValue("failure");
    134         verify(observer, never()).onChanged(any());
    135     }
    136 
    137     @Test
    138     public void testSourceInactive() {
    139         Observer observer = mock(Observer.class);
    140         mMediator.addSource(mSource, observer);
    141         assertThat(mSourceActive, is(true));
    142         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    143         assertThat(mSourceActive, is(false));
    144         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    145         assertThat(mSourceActive, is(true));
    146     }
    147 
    148     @Test
    149     public void testNoLeakObserver() {
    150         // Imitates a destruction of a ViewModel: a listener of LiveData is destroyed,
    151         // a reference to MediatorLiveData is cleaned up. In this case we shouldn't leak
    152         // MediatorLiveData as an observer of mSource.
    153         assertThat(mSource.hasObservers(), is(false));
    154         Observer observer = mock(Observer.class);
    155         mMediator.addSource(mSource, observer);
    156         assertThat(mSource.hasObservers(), is(true));
    157         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    158         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    159         mMediator = null;
    160         assertThat(mSource.hasObservers(), is(false));
    161     }
    162 
    163     @Test
    164     public void testMultipleSources() {
    165         Observer observer1 = mock(Observer.class);
    166         mMediator.addSource(mSource, observer1);
    167         MutableLiveData<Integer> source2 = new MutableLiveData<>();
    168         Observer observer2 = mock(Observer.class);
    169         mMediator.addSource(source2, observer2);
    170         mSource.setValue("flatfoot");
    171         verify(observer1).onChanged("flatfoot");
    172         verify(observer2, never()).onChanged(any());
    173         reset(observer1, observer2);
    174         source2.setValue(1703);
    175         verify(observer1, never()).onChanged(any());
    176         verify(observer2).onChanged(1703);
    177         reset(observer1, observer2);
    178         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    179         mSource.setValue("failure");
    180         source2.setValue(0);
    181         verify(observer1, never()).onChanged(any());
    182         verify(observer2, never()).onChanged(any());
    183     }
    184 
    185     @Test
    186     public void removeSourceDuringOnActive() {
    187         // to trigger ConcurrentModificationException,
    188         // we have to call remove from a collection during "for" loop.
    189         // ConcurrentModificationException is thrown from next() method of an iterator
    190         // so this modification shouldn't be at the last iteration,
    191         // because if it is a last iteration, then next() wouldn't be called.
    192         // And the last: an order of an iteration over sources is not defined,
    193         // so I have to call it remove operation  from all observers.
    194         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    195         Observer<String> removingObserver = new Observer<String>() {
    196             @Override
    197             public void onChanged(@Nullable String s) {
    198                 mMediator.removeSource(mSource);
    199             }
    200         };
    201         mMediator.addSource(mSource, removingObserver);
    202         MutableLiveData<String> source2 = new MutableLiveData<>();
    203         source2.setValue("nana");
    204         mMediator.addSource(source2, removingObserver);
    205         mSource.setValue("petjack");
    206         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    207     }
    208 
    209     @Test(expected = IllegalArgumentException.class)
    210     public void reAddSameSourceWithDifferentObserver() {
    211         mMediator.addSource(mSource, mock(Observer.class));
    212         mMediator.addSource(mSource, mock(Observer.class));
    213     }
    214 
    215     @Test
    216     public void addSameSourceWithSameObserver() {
    217         Observer observer = mock(Observer.class);
    218         mMediator.addSource(mSource, observer);
    219         mMediator.addSource(mSource, observer);
    220         // no exception was thrown
    221     }
    222 
    223     @Test
    224     public void addSourceDuringOnActive() {
    225         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    226         mSource.setValue("a");
    227         mMediator.addSource(mSource, new Observer<String>() {
    228             @Override
    229             public void onChanged(@Nullable String s) {
    230                 MutableLiveData<String> source = new MutableLiveData<>();
    231                 source.setValue("b");
    232                 mMediator.addSource(source, new Observer<String>() {
    233                     @Override
    234                     public void onChanged(@Nullable String s) {
    235                         mMediator.setValue("c");
    236                     }
    237                 });
    238             }
    239         });
    240         mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    241         assertThat(mMediator.getValue(), is("c"));
    242     }
    243 
    244 }
    245