From ca6c67d8ae1a5b38c910a3c70bfbaf8774f822ad Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 3 Jan 2015 01:35:10 -0500 Subject: [PATCH] Fix up tests for the RefreshManager I'm noticing I write much better code when I have tests in place, it takes much longer to do though... --- .../manager/RefreshManagerTest.java | 39 ------- .../downloader/manager/RefreshManagerSpek.kt | 108 ++++++++++++++++++ .../downloader/manager/RefreshManager.kt | 34 +++--- 3 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 app-test/src/test/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManagerSpek.kt diff --git a/app-test/src/test/java/org/bspeice/minimalbible/test/activity/downloader/manager/RefreshManagerTest.java b/app-test/src/test/java/org/bspeice/minimalbible/test/activity/downloader/manager/RefreshManagerTest.java index 1f3b60f..104635b 100644 --- a/app-test/src/test/java/org/bspeice/minimalbible/test/activity/downloader/manager/RefreshManagerTest.java +++ b/app-test/src/test/java/org/bspeice/minimalbible/test/activity/downloader/manager/RefreshManagerTest.java @@ -11,15 +11,11 @@ import org.crosswire.jsword.book.install.Installer; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Singleton; @@ -29,7 +25,6 @@ import dagger.ObjectGraph; import dagger.Provides; import rx.functions.Action1; -import static com.jayway.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -100,40 +95,6 @@ public class RefreshManagerTest implements Injector { verify(mockInstaller).getBooks(); } - @Test - public void testRefreshSeparateThread() { - mockInstaller = mock(Installer.class); - final List bookList = new ArrayList(); - when(mockInstaller.getBooks()).thenAnswer(new Answer>() { - @Override - public List answer(InvocationOnMock invocationOnMock) throws Throwable { - Thread.sleep(1000); // Just long enough to give us a gap between - // refresh start and complete - return bookList; - } - }); - - Collection mockInstallers = new ArrayList(); - mockInstallers.add(mockInstaller); - - RMTModules modules = new RMTModules(mockInstallers); - mObjectGraph = ObjectGraph.create(modules); - - // And the actual test - mObjectGraph.inject(this); - - // So the refresh should be kicked off at the constructor, meaning that it's not "complete" - assertFalse(rM.getRefreshComplete().get()); - - // But, if it's on another thread, it should finish up eventually, right? - await().atMost(5, TimeUnit.SECONDS).until(new Callable() { - @Override - public Boolean call() throws Exception { - return rM.getRefreshComplete().get(); - } - }); - } - /** * Test the conditions are right for downloading * I'd like to point out that I can test all of this without requiring mocking of diff --git a/app-test/src/test/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManagerSpek.kt b/app-test/src/test/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManagerSpek.kt new file mode 100644 index 0000000..8f4a040 --- /dev/null +++ b/app-test/src/test/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManagerSpek.kt @@ -0,0 +1,108 @@ +package org.bspeice.minimalbible.activity.downloader.manager + +import org.jetbrains.spek.api.Spek +import org.bspeice.minimalbible.activity.downloader.DownloadPrefs +import java.util.Calendar +import org.crosswire.jsword.book.install.Installer +import org.mockito.Mockito.* +import org.mockito.Matchers.* +import rx.schedulers.Schedulers +import java.util.concurrent.atomic.AtomicBoolean +import com.jayway.awaitility.Awaitility +import java.util.concurrent.TimeUnit +import rx.Subscriber +import org.crosswire.jsword.book.Book + +/** + * Created by bspeice on 1/3/15. + */ + +class RefreshManagerSpek() : Spek() {{ + + fun buildRefreshmanager(installers: List, prefs: DownloadPrefs) = + RefreshManager(installers, listOf(""), prefs, null) + + fun buildMockPrefs(): DownloadPrefs { + val currentTime = Calendar.getInstance().getTime().getTime() + val eighteenDaysAgo = currentTime - 1555200 + val mockPrefs = mock(javaClass()) + `when`(mockPrefs.downloadRefreshedOn()) + .thenReturn(eighteenDaysAgo) + + return mockPrefs + } + + given("a mock installer") { + val installer = mock(javaClass()) + + on("creating a new RefreshManager and mock preferences") { + val mockPrefs = buildMockPrefs() + val rM = buildRefreshmanager(listOf(installer, installer), mockPrefs) + + it("should not have updated the prefs as part of the constructor") { + verify(mockPrefs, never()) + .downloadRefreshedOn(anyLong()) + } + } + + on("creating a new RefreshManager and mock preferences") { + val mockPrefs = buildMockPrefs() + val rM = buildRefreshmanager(listOf(installer, installer), mockPrefs) + reset(mockPrefs) + + it("should not update the prefs after the first installer") { + // The process to do actually validate this is tricky. We have to block + // the Observable from producing before we can validate the preferences - + // I don't want to race the Observable since it's possible it's on another thread. + // So, we use backpressure (request(1)) to force the observable to + // produce only one result. + val success = AtomicBoolean(false) + rM.availableModules + .subscribe(object : Subscriber>>() { + override fun onCompleted() { + } + + override fun onError(e: Throwable?) { + } + + override fun onStart() { + super.onStart() + request(1) + } + + override fun onNext(t: Map>?) { + // Verify the mock - if verification doesn't pass, we won't reach + // the end of this method and set our AtomicBoolean to true + verify(mockPrefs, never()) + .downloadRefreshedOn(anyLong()) + success.set(true) + } + }) + + Awaitility.waitAtMost(2, TimeUnit.SECONDS) + .untilTrue(success) + } + } + + on("creating a new RefreshManager and mock preferences") { + val mockPrefs = buildMockPrefs() + val rM = buildRefreshmanager(listOf(installer, installer), mockPrefs) + reset(mockPrefs) + + it("should update the prefs after completed") { + val complete = AtomicBoolean(false) + rM.availableModules.observeOn(Schedulers.immediate()) + .subscribe({}, {}, { + complete.set(true) + }) + + Awaitility.waitAtMost(3, TimeUnit.SECONDS) + .untilTrue(complete) + + verify(mockPrefs, times(1)) + .downloadRefreshedOn(anyLong()) + } + } + } +} +} diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManager.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManager.kt index 9e1848c..92e4984 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManager.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/downloader/manager/RefreshManager.kt @@ -8,7 +8,6 @@ import java.util.Calendar import org.bspeice.minimalbible.activity.downloader.DownloadPrefs import android.net.ConnectivityManager import org.crosswire.jsword.book.BookComparators -import java.util.Date /** * Created by bspeice on 10/22/14. @@ -19,19 +18,24 @@ class RefreshManager(val installers: Collection, val prefs: DownloadPrefs, val connManager: ConnectivityManager?) { + val currentTime = Calendar.getInstance().getTime().getTime() + val fifteenDaysAgo = currentTime - 1296000 + val availableModules: Observable>> = Observable.from(installers) .map { - if (doReload()) { + if (performReload()) it.reloadBookList() - prefs.downloadRefreshedOn(Date().getTime()) - } - val validBooks = it.getBooks() - .filterNot { exclude contains it.getInitials() } - mapOf(Pair(it, validBooks)) + + // TODO: mapOf(it to booksFromInstaller) + mapOf(Pair(it, + booksFromInstaller(it, exclude))) } + // Don't update timestamps until done. Additionally, make this operation + // part of the pipeline, so it remains a cold observable + .doOnCompleted { prefs.downloadRefreshedOn(currentTime) } .subscribeOn(Schedulers.io()) - .cache(); + .cache() val flatModules: Observable = availableModules @@ -42,9 +46,7 @@ class RefreshManager(val installers: Collection, val flatModulesSorted = flatModules.toSortedList {(book1, book2) -> BookComparators.getInitialComparator().compare(book1, book2) - }; - - val fifteenDaysAgo = Calendar.getInstance().getTime().getTime() - 1296000 + } fun doReload(downloadEnabled: Boolean, lastUpdated: Long, networkState: Int? = ConnectivityManager.TYPE_DUMMY): Boolean = @@ -55,9 +57,13 @@ class RefreshManager(val installers: Collection, else false - fun doReload(): Boolean = doReload(prefs.hasEnabledDownload(), - prefs.downloadRefreshedOn(), - connManager?.getActiveNetworkInfo()?.getType()) + fun performReload() = + doReload(prefs.hasEnabledDownload(), + prefs.downloadRefreshedOn(), + connManager?.getActiveNetworkInfo()?.getType()) + + fun booksFromInstaller(inst: Installer, exclude: List) = + inst.getBooks().filterNot { exclude contains it.getInitials() } fun installerFromBook(b: Book): Observable = Observable.just( availableModules.filter {