/* * Copyright 2015 Rayco AraƱa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.raycoarana.baindo; import android.os.Handler; import android.os.Looper; import com.raycoarana.baindo.test.UnitTestSuite; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({Handler.class, Looper.class}) public class BaindoWorkDispatcherTest extends UnitTestSuite { private static final int NEVER = 0; private static final int ONCE = 1; private static final long ONE_SECOND = 1000; @Mock private AndroidProvider mAndroidProvider; @Mock private Runnable mSomeWork; private Looper mBackgroundThreadLooper = PowerMockito.mock(Looper.class); private Handler mBackgroundThreadHandler = PowerMockito.mock(Handler.class); private Handler mUIThreadHandler = PowerMockito.mock(Handler.class); private BaindoWorkDispatcher mBaindoWorkDispatcher; private Thread mSomeThread; private boolean mKeepLooping = true; @Test public void shouldInitializeTheBackgroundThread() { givenABinderDispatcher(); thenBackgroundThreadIsInitialized(); } @Test public void shouldAbortInitializeOfBackgroundThreadIfInterrupted() { givenThatBinderDispatcherIsWaitingToBeInitialized(); whenThreadIsInterrupted(); thenThreadFinishTheInitialization(); } @Test(timeout = ONE_SECOND) public void shouldStopBackgroundThreadOnDestroy() { givenThatBackgroundThreadLooperKeepsLooping(); givenALooperForBackgroundThread(); givenABinderDispatcher(); whenOnDestroy(); thenBackgroundThreadIsFinished(); } @Test public void shouldDispatchWorkToMainThreadWhenOnBackgroundThread() { givenThatCurrentThreadIsBackgroundThread(); givenABinderDispatcher(); whenDoInUIThread(); thenWorkIsDispatchedToMainThread(); } @Test public void shouldExecuteWorkDirectlyWhenOnBackgroundThread() { givenThatCurrentThreadIsBackgroundThread(); givenABinderDispatcher(); whenDoInBackgroundThread(); thenWorkIsExecutedDirectly(); } @Test public void shouldDispatchWorkToBackgroundThreadWhenOnMainThread() { givenThatCurrentThreadIsMainThread(); givenABinderDispatcher(); whenDoInBackgroundThread(); thenWorkIsDispatchedToBackgroundThread(); } @Test public void shouldExecuteWorkDirectlyWhenOnMainThread() { givenThatCurrentThreadIsMainThread(); givenABinderDispatcher(); whenDoInUIThread(); thenWorkIsExecutedDirectly(); } private void givenABinderDispatcher() { when(mAndroidProvider.buildHandlerWithMainLooper()).thenReturn(mUIThreadHandler); when(mAndroidProvider.buildHandler()).thenReturn(mBackgroundThreadHandler); givenAWaitingForBackgroundThreadBinderDispatcher(); } @SuppressWarnings({"Anonymous2MethodRef", "Convert2Lambda"}) private void givenThatBinderDispatcherIsWaitingToBeInitialized() { mSomeThread = new Thread(new Runnable() { @Override public void run() { givenAWaitingForBackgroundThreadBinderDispatcher(); } }); mSomeThread.start(); } @SuppressWarnings({"InfiniteLoopStatement", "StatementWithEmptyBody", "Convert2Lambda"}) private void givenThatBackgroundThreadLooperKeepsLooping() { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { mKeepLooping = false; return null; } }).when(mBackgroundThreadLooper).quit(); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { while (mKeepLooping) { //Do nothing } return null; } }).when(mAndroidProvider).loop(); } private void givenALooperForBackgroundThread() { when(mBackgroundThreadHandler.getLooper()).thenReturn(mBackgroundThreadLooper); } private void givenAWaitingForBackgroundThreadBinderDispatcher() { mBaindoWorkDispatcher = new BaindoWorkDispatcher(mAndroidProvider); } private void givenThatCurrentThreadIsBackgroundThread() { mockIsCurrentTheMainThreadTo(false); } private void givenThatCurrentThreadIsMainThread() { mockIsCurrentTheMainThreadTo(true); } private void mockIsCurrentTheMainThreadTo(boolean value) { when(mAndroidProvider.isCurrentTheMainThread()).thenReturn(value); } private void whenThreadIsInterrupted() { mSomeThread.interrupt(); } private void whenOnDestroy() { mBaindoWorkDispatcher.onDestroy(); } private void whenDoInUIThread() { mBaindoWorkDispatcher.doInUIThread(mSomeWork); } private void whenDoInBackgroundThread() { mBaindoWorkDispatcher.doInBackgroundThread(mSomeWork); } private void thenBackgroundThreadIsInitialized() { try { mBaindoWorkDispatcher.mBackgroundThread.join(); } catch (InterruptedException e) { throw new IllegalStateException("BinderDispatcher doesn't finish it's initialization"); } verify(mAndroidProvider).loop(); } private void thenThreadFinishTheInitialization() { try { mSomeThread.join(ONE_SECOND); assertFalse(mSomeThread.isAlive()); } catch (InterruptedException e) { throw new IllegalStateException("BinderDispatcher doesn't finish it's initialization"); } } private void thenBackgroundThreadIsFinished() { try { mBaindoWorkDispatcher.mBackgroundThread.join(ONE_SECOND); assertFalse(mBaindoWorkDispatcher.mBackgroundThread.isAlive()); } catch (InterruptedException e) { throw new IllegalStateException("BinderDispatcher doesn't finish background thread."); } } private void thenWorkIsDispatchedToBackgroundThread() { verifyDispatch(ONCE, NEVER, NEVER); } private void thenWorkIsExecutedDirectly() { verifyDispatch(NEVER, NEVER, ONCE); } private void thenWorkIsDispatchedToMainThread() { verifyDispatch(NEVER, ONCE, NEVER); } private void verifyDispatch(int backgroundThreadExecutions, int uiThreadExecutions, int directExecutions) { verify(mBackgroundThreadHandler, times(backgroundThreadExecutions)).post(mSomeWork); verify(mUIThreadHandler, times(uiThreadExecutions)).post(mSomeWork); verify(mSomeWork, times(directExecutions)).run(); } }