-
Notifications
You must be signed in to change notification settings - Fork 499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hookup MvRxMocker and add tests #81
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ import io.reactivex.disposables.CompositeDisposable | |
import io.reactivex.disposables.Disposable | ||
import io.reactivex.schedulers.Schedulers | ||
import io.reactivex.subjects.BehaviorSubject | ||
import java.util.LinkedList | ||
import java.util.* | ||
|
||
/** | ||
* This is a container class around the actual state itself. It has a few optimizations to ensure | ||
|
@@ -15,7 +15,11 @@ import java.util.LinkedList | |
* conditions with each other. | ||
* | ||
*/ | ||
internal open class MvRxStateStore<S : Any>(initialState: S) : Disposable { | ||
internal open class MvRxStateStore<S : Any>( | ||
initialState: S | ||
) : Disposable { | ||
|
||
internal val isMocked = MvRxMocker.enabled | ||
/** | ||
* The subject is where state changes should be pushed to. | ||
*/ | ||
|
@@ -39,8 +43,8 @@ internal open class MvRxStateStore<S : Any>(initialState: S) : Disposable { | |
* current state. | ||
*/ | ||
val state: S | ||
// value must be present here, since the subject is created with initialState | ||
get() = subject.value!! | ||
// value must be present here, since the subject is created with initialState | ||
get() = MvRxMocker.getMockedState(this) ?: subject.value!! | ||
|
||
init { | ||
|
||
|
@@ -57,8 +61,10 @@ internal open class MvRxStateStore<S : Any>(initialState: S) : Disposable { | |
* are guaranteed to run before the get block is run. | ||
*/ | ||
fun get(block: (S) -> Unit) { | ||
jobs.enqueueGetStateBlock(block) | ||
flushQueueSubject.onNext(Unit) | ||
MvRxMocker.getMockedState(this)?.let(block) ?: run { | ||
jobs.enqueueGetStateBlock(block) | ||
flushQueueSubject.onNext(Unit) | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -72,6 +78,8 @@ internal open class MvRxStateStore<S : Any>(initialState: S) : Disposable { | |
* all of the code required. | ||
*/ | ||
fun set(stateReducer: S.() -> S) { | ||
if (MvRxMocker.getMockedState(this) != null) return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you see reasons to only block this if mocked state is set? I think it should be just
Because the view model or fragment might do things to set state before the mock is in place (the mock can only be set once the view model is created). Since setting state is async and can trigger subscribe callbacks it would be nice to eliminate any side effects from that |
||
|
||
jobs.enqueueSetStateBlock(stateReducer) | ||
flushQueueSubject.onNext(Unit) | ||
} | ||
|
@@ -131,8 +139,8 @@ internal open class MvRxStateStore<S : Any>(initialState: S) : Disposable { | |
val blocks = jobs.dequeueAllSetStateBlocks() ?: return | ||
|
||
blocks | ||
.fold(state) { state, reducer -> state.reducer() } | ||
.run { subject.onNext(this) } | ||
.fold(state) { state, reducer -> state.reducer() } | ||
.run { subject.onNext(this) } | ||
} | ||
|
||
private fun handleError(throwable: Throwable) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.airbnb.mvrx | ||
|
||
|
||
/** | ||
* This singleton can be used to provide mocked states to viewModels for testing. | ||
* State can be directly set or set via arguments. Set the mocker to enabled | ||
* to have the viewModels use these mocked states. | ||
*/ | ||
object MvRxMocker { | ||
|
||
/** | ||
* Enable or disable whether the viewModels use mocked states | ||
*/ | ||
var enabled: Boolean = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using static variables can cause a lot of headaches for tests. This will not be reset between tests. So if some test turns this on and forgets to turn it off it will stay on for all remaining tests. |
||
private val mockedState: MutableMap<MvRxStateStore<*>, MvRxState?> = mutableMapOf() | ||
|
||
/** | ||
* Set a mock state on a viewModel directly | ||
*/ | ||
fun <S : MvRxState> setMockedState(viewModel: BaseMvRxViewModel<S>, state: S?) { | ||
mockedState[viewModel.stateStore] = state | ||
} | ||
|
||
/** | ||
* Set a mock state on a viewModel via arguments | ||
*/ | ||
fun <S : MvRxState> setMockedStateFromArgs(viewModel: BaseMvRxViewModel<S>, args: Any?) { | ||
setMockedState(viewModel, _initialStateProvider(viewModel.state::class.java, args)) | ||
} | ||
|
||
/** | ||
* Return the mocked state for a viewModel if one has been set, otherwise return null | ||
*/ | ||
internal fun <S : Any> getMockedState(stateStore: MvRxStateStore<S>): S? { | ||
@Suppress("UNCHECKED_CAST") | ||
return if (enabled) mockedState[stateStore] as? S else null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.airbnb.mvrx | ||
|
||
import junit.framework.Assert | ||
import org.junit.Test | ||
|
||
class MvRxMockerTest : BaseTest() { | ||
|
||
private val initialState = TestState("initial", 0) | ||
private val mockState = TestState("updated", 1) | ||
|
||
@Test | ||
fun testDisabled() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice tests! |
||
MvRxMocker.enabled = false | ||
|
||
val viewModel = TestViewModel(initialState) | ||
MvRxMocker.setMockedState(viewModel, mockState) | ||
|
||
withState(viewModel) { state -> | ||
Assert.assertEquals(state, initialState) | ||
} | ||
} | ||
|
||
@Test | ||
fun testEnabled() { | ||
MvRxMocker.enabled = true | ||
|
||
val viewModel = TestViewModel(initialState) | ||
MvRxMocker.setMockedState(viewModel, mockState) | ||
|
||
withState(viewModel) { state -> | ||
Assert.assertEquals(mockState, state) | ||
} | ||
} | ||
|
||
@Test | ||
fun testMockNotSet() { | ||
MvRxMocker.enabled = true | ||
|
||
val viewModel = TestViewModel(initialState) | ||
|
||
withState(viewModel) { state -> | ||
Assert.assertEquals(initialState, state) | ||
} | ||
} | ||
|
||
@Test | ||
fun testMockArgs() { | ||
MvRxMocker.enabled = true | ||
|
||
val viewModel = TestViewModel(initialState) | ||
val args = ParcelableArgs("test args") | ||
MvRxMocker.setMockedStateFromArgs(viewModel, args) | ||
|
||
withState(viewModel) { state -> | ||
Assert.assertEquals(TestState(args), state) | ||
} | ||
} | ||
|
||
private class TestViewModel(initialState: TestState) : TestMvRxViewModel<TestState>(initialState) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need
get() =
, otherwise this won't update