-
Notifications
You must be signed in to change notification settings - Fork 0
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
[WIP] Unit tests/basics #28
base: develop
Are you sure you want to change the base?
Changes from all commits
485267b
6f489a7
dd44dbe
861f580
1dad583
59d47f1
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 |
---|---|---|
|
@@ -16,7 +16,7 @@ class HomeViewModel( | |
private val loadImageUseCase: LoadImageUseCase, | ||
private val sessionManager: SessionManager | ||
) : ViewModel() { | ||
private val _isLoading = MutableLiveData<Boolean>() | ||
private val _isLoading = MutableLiveData<Boolean>(false) | ||
val isLoading: LiveData<Boolean> = _isLoading | ||
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. I think this is supposed to have a getter instead of assigned MutableLiveData value, right? |
||
|
||
private val _imageData = MutableLiveData<Image>() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.tapptitude.featurehome | ||
|
||
import com.tapptitude.core.usecase.LoadImageUseCase | ||
import com.tapptitude.featurehome.fake.FAKE_IMAGE | ||
import com.tapptitude.featurehome.fake.FakeImageRepository | ||
import com.tapptitude.featurehome.fake.FakeSessionManager | ||
import com.tapptitude.featurehome.presentation.HomeViewModel | ||
import com.tapptitude.session.SessionManager | ||
import org.junit.After | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Before | ||
import org.junit.Test | ||
|
||
internal class HomeViewModelTest { | ||
|
||
private val loadImageUsecase: LoadImageUseCase = LoadImageUseCase( | ||
imageRepository = FakeImageRepository(requestDelay = 0, fakeImage = FAKE_IMAGE) | ||
) | ||
private lateinit var fakeSessionManager: SessionManager | ||
private lateinit var viewModel: HomeViewModel | ||
|
||
@Before | ||
fun setup() { | ||
fakeSessionManager = FakeSessionManager() | ||
|
||
viewModel = HomeViewModel( | ||
loadImageUseCase = loadImageUsecase, | ||
sessionManager = fakeSessionManager, | ||
) | ||
} | ||
|
||
@After | ||
fun tearDown() = Unit | ||
|
||
@Test | ||
fun `initial state is not loading`() { | ||
assertEquals( | ||
false, | ||
viewModel.isLoading.value, | ||
) | ||
} | ||
|
||
@Test | ||
fun `assert no image is present at start`() { | ||
// We do not make any request. We shouldn't have any image | ||
assertEquals( | ||
null, | ||
viewModel.imageData.value, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.tapptitude.featurehome.fake | ||
|
||
import com.tapptitude.core.model.Image | ||
|
||
internal val FAKE_IMAGE = Image("test_url") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.tapptitude.featurehome.fake | ||
|
||
import com.tapptitude.core.model.Image | ||
import com.tapptitude.core.repository.ImageRepository | ||
import kotlinx.coroutines.delay | ||
|
||
|
||
internal class FakeImageRepository( | ||
var requestDelay: Long = 0, | ||
private val fakeImage: Image = Image("test_url"), | ||
) : ImageRepository { | ||
|
||
override suspend fun getRandomImage(): Image { | ||
delay(requestDelay) | ||
return fakeImage | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.tapptitude.featurehome.fake | ||
|
||
import com.tapptitude.session.SessionManager | ||
import com.tapptitude.session.model.LoggedIn | ||
import com.tapptitude.session.model.LoggedOut | ||
import com.tapptitude.session.model.LoginState | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
internal class FakeSessionManager : SessionManager { | ||
private val _currentLoginStateFlow: MutableStateFlow<LoginState> = MutableStateFlow( | ||
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. What's the |
||
LoggedOut("") | ||
) | ||
|
||
override val currentLoginStateFlow: StateFlow<LoginState> | ||
get() = _currentLoginStateFlow | ||
|
||
override fun onLoggedIn(accessToken: String, userId: String) { | ||
_currentLoginStateFlow.value = LoggedIn( | ||
sessionToken = accessToken, | ||
userId = userId | ||
) | ||
} | ||
|
||
override fun onLoggedOut(lastUserId: String?) { | ||
_currentLoginStateFlow.value = LoggedOut( | ||
null | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package com.tapptitude.network | ||
|
||
import com.tapptitude.network.fake.FakeSessionManager | ||
import com.tapptitude.network.fake.IgnoredCallback | ||
import com.tapptitude.network.interceptor.SessionInterceptor | ||
import com.tapptitude.session.SessionManager | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import okhttp3.mockwebserver.MockWebServer | ||
import org.junit.After | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Before | ||
import org.junit.Test | ||
|
||
/** | ||
* Underneath, Retrofit2 uses OkHttp for requests. | ||
* | ||
* Thus, if we want to test an interceptor, it is enough to use a OkHttp object. | ||
* No need to create an instance of retrofit | ||
*/ | ||
internal class SessionInterceptorTest { | ||
private lateinit var interceptor: SessionInterceptor | ||
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. I prefer leaving an empty line between class declaration and first field/method. Makes it feel a bit cleaner :D |
||
private lateinit var sessionManager: SessionManager | ||
private lateinit var okHttpClient: OkHttpClient | ||
private lateinit var mockWebServer: MockWebServer | ||
|
||
@Before | ||
fun `before each test, do a fresh set up`() { | ||
sessionManager = FakeSessionManager() | ||
interceptor = SessionInterceptor(sessionManager) | ||
okHttpClient = OkHttpClient.Builder() | ||
.addInterceptor(interceptor) | ||
.build() | ||
|
||
mockWebServer = MockWebServer() | ||
mockWebServer.start() | ||
} | ||
|
||
@After | ||
fun `after each test, clean up the resource`() { | ||
mockWebServer.shutdown() | ||
} | ||
|
||
@Test | ||
fun `assert when logged in the authorization header is set`() { | ||
val mockUserId = "mocked_user_id" | ||
val mockedAccessToken = "mocked_access_token" | ||
|
||
// we logged in the user | ||
sessionManager.onLoggedIn(accessToken = mockedAccessToken, userId = mockUserId) | ||
|
||
val request = Request.Builder() | ||
.url(mockWebServer.url("test")) | ||
.get() | ||
.build() | ||
|
||
okHttpClient.newCall(request).enqueue(IgnoredCallback) | ||
|
||
val receivedRequest = mockWebServer.takeRequest() | ||
|
||
assertTrue( | ||
"The Authorization token is not present!", | ||
receivedRequest.headers.contains( | ||
SessionInterceptor.HEADER_AUTHORIZATION to mockedAccessToken | ||
) | ||
) | ||
} | ||
|
||
@Test | ||
fun `assert when logged out the authorization header is NOT set`() { | ||
sessionManager.onLoggedOut(null) | ||
|
||
val request = Request.Builder() | ||
.url(mockWebServer.url("test")) | ||
.get() | ||
.build() | ||
|
||
okHttpClient.newCall(request).enqueue(IgnoredCallback) | ||
|
||
val receivedRequest = mockWebServer.takeRequest() | ||
|
||
assertFalse( | ||
"The authorization token should not be set", | ||
receivedRequest.headers.any { it.first == SessionInterceptor.HEADER_AUTHORIZATION } | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.tapptitude.network.fake | ||
|
||
import com.tapptitude.session.SessionManager | ||
import com.tapptitude.session.model.LoggedIn | ||
import com.tapptitude.session.model.LoggedOut | ||
import com.tapptitude.session.model.LoginState | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
internal class FakeSessionManager : SessionManager { | ||
private val _currentLoginStateFlow: MutableStateFlow<LoginState> = MutableStateFlow( | ||
LoggedOut("") | ||
) | ||
|
||
override val currentLoginStateFlow: StateFlow<LoginState> | ||
get() = _currentLoginStateFlow | ||
|
||
override fun onLoggedIn(accessToken: String, userId: String) { | ||
_currentLoginStateFlow.value = LoggedIn( | ||
sessionToken = accessToken, | ||
userId = userId | ||
) | ||
} | ||
|
||
override fun onLoggedOut(lastUserId: String?) { | ||
_currentLoginStateFlow.value = LoggedOut( | ||
null | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.tapptitude.network.fake | ||
|
||
import java.io.IOException | ||
import okhttp3.Call | ||
import okhttp3.Callback | ||
import okhttp3.Response | ||
|
||
/** | ||
* An empty [Callback] to be used in testing. | ||
* Does nothing. | ||
*/ | ||
internal object IgnoredCallback : Callback { | ||
override fun onFailure(call: Call, e: IOException) = Unit | ||
override fun onResponse(call: Call, response: Response) = Unit | ||
} |
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.
Shouldn't it also have the
operator
modifier? 🤔