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