Status Update
Comments
ma...@google.com <ma...@google.com>
je...@google.com <je...@google.com> #2
Branch: androidx-master-dev
commit b90079595f33f58fece04026a97faa0d243acdb1
Author: Yuichi Araki <yaraki@google.com>
Date: Wed Sep 18 16:55:49 2019
Change the way to detect mismatch between POJO and query
This fixes cursor mismatch warnings with expandProjection.
Bug: 140759491
Test: QueryMethodProcessorTest
Change-Id: I7659002e5e0d1ef60fc1af2a625c0c36da0664d8
M room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
M room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
M room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
M room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
M room/compiler/src/test/kotlin/androidx/room/testing/TestProcessor.kt
je...@google.com <je...@google.com> #3
me...@thomaskeller.biz <me...@thomaskeller.biz> #4
Branch: androidx-master-dev
commit bdde5a1a970ddc9007b28de4aa29d60ffa588f08
Author: Yigit Boyar <yboyar@google.com>
Date: Thu Apr 16 16:47:05 2020
Re-factor how errors are dismissed when query is re-written
This CL changes how we handle errors/warnings if query is
re-written.
There was a bug in expandProjection where we would report warnings
for things that Room already fixes automatically (
The solution to that problem (I7659002e5e0d1ef60fc1af2a625c0c36da0664d8)
solved it by deferring validating of columns until after re-write
decision is made. Unfortunately, this required changing PojoRowAdapter
to have a dummy mapping until it is validating, make it hard to use
as it does have a non-null mapping which is not useful.
This CL partially reverts that change and instead rely on the log
deferring logic we have in Context. This way, we don't need to break
the stability of PojoRowAdapter while still having the ability to
drop warnings that room fixes. This will also play nicer when we
have different query re-writing options that can use more information
about the query results.
Bug: 153387066
Bug: 140759491
Test: existing tests pass
Change-Id: I2ec967c763d33d7a3ff02c1a13c6953b460d1e5f
M room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
M room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
M room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
M room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
je...@google.com <je...@google.com> #5
Could you provide a small sample to illustrate the scenario you would like to test, and the thing that's not possible to test now?
me...@thomaskeller.biz <me...@thomaskeller.biz> #6
Yes, for example I have a Fragment with a Compose UI, set up like this:
class FooFragment : Fragment() {
private lateinit var binding: FragmentFooBinding
private val viewModel: FooViewModel by viewModels()
private val args: FooFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.composeView.setContent {
MdcTheme {
FooScreen(viewModel)
}
}
viewModel.onAction(FooActions.OnViewCreated(args))
}
override fun onPause() {
viewModel.onAction(FooActions.OnPause)
super.onPause()
}
}
What I'm doing right now in my Compose test is that I manually trigger the specific actions to either feed the arguments to my ViewModel or to manually trigger the OnPause
lifecycle action. This however doesn't test the real implementation! Since the fragment code isn't run, I cannot verify that I trigger the specific actions in the right lifecycle methods (or, that anybody moved things around).
Similar issues arise once I want to trigger and test for jetpack navigation events via a TestNavController
, i.e. testing regular nav graphs in conjunction with the VM / Fragment calling code.
me...@thomaskeller.biz <me...@thomaskeller.biz> #7
Hi, is this still a Won't Fix for you? This issue is still present and needs awkward workarounds.
je...@google.com <je...@google.com> #8
Apologies, this slipped off my radar.
If you add the FooFragment to a test activity and then use createAndroidComposeRule<YourTestActivity>()
, it should execute everything necessary to get the activity in the resumed state, which includes onViewCreated
.
For onPause
, however, you need a little more control. What you can do there is create an "empty" compose test rule, one that does not start an activity for you at all, and move your fragment through all states manually (like you would do for View-based UIs). For example:
@get:Rule
val composeRule = createEmptyComposeRule()
@Test
fun test() {
with(launchFragmentInContainer<FooFragment>()) {
// composeRule.onNode(..).assert(..), etc
moveToState(State.STARTED)
// assertions, etc.
}
}
me...@thomaskeller.biz <me...@thomaskeller.biz> #9
The problem is really interaction. The OnViewCreated
action would kick-off a change that would result in a change of the downstream state that FooScreen
would subscribe to. I assume that even though I'd add FooFragment
to the Activity-under-test, I'd still have to call composeRule.setContent {}
, because the Fragment's setContent
call on the ComposeView
still knows nothing of the Recomposer
from the test rule, so these two things would still be detached from one another.
That's what I was referring to - if the current implementation of AndroidComposeTestRule
would be a bit more open and would also allow the wrapping of ComposeView
intances (from Activity
s, Fragment
s or else), it would be of much help, especially for cases where developers weren't able to convert a complete screen to Compose yet, but start bottom up with small building blocks, like single views or fragments.
My go-to-solution is now to have a separate Fragment
test that mocks out the ViewModel and tests the interactions in isolation, and then have another Compose+ViewModel test which does the other part.
je...@google.com <je...@google.com> #10
Ah, I think I see the misunderstanding now.
You don't need to call AndroidComposeTestRule.setContent
if your Activity or Fragment already sets the content. If you look closely to the return type of createEmptyComposeRule()
, you'll find that it doesn't even have a setContent
method.
The test rule takes care of wiring everything together. If you're interested to see how, take a look at
@get:Rule(order = 0)
val activityScenarioRule = ActivityScenarioRule<CustomActivity>()
@get:Rule(order = 1)
val composeTestRule = createEmptyComposeRule()
Please take a look at
ap...@google.com <ap...@google.com> #11
Branch: androidx-main
commit ff4469e78695de2436bdbe8822722f7c5e22de29
Author: Jelle Fresen <jellefresen@google.com>
Date: Tue Feb 15 14:22:51 2022
Test Compose when content set by a Fragment
This shows we can test Compose content if it is set by a Fragment
Fix: 199631334
Test: ./gradlew compose:ui:ui-test-junit4:cC
Change-Id: I9c79a1e240734be481c54d24c14111750c916f45
M compose/ui/ui-test-junit4/build.gradle
A compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
je...@google.com <je...@google.com>
me...@thomaskeller.biz <me...@thomaskeller.biz> #12
Many thanks for the example, will try that out!
me...@thomaskeller.biz <me...@thomaskeller.biz> #13
A small addition - when my Fragment is in Lifecycle.State.STARTED
, compose will tell me this:
java.lang.IllegalStateException: No compose views found in the app. Is your Activity resumed?
at androidx.compose.ui.test.TestContext.getAllSemanticsNodes$ui_test_release(TestOwner.kt:96)
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchSemanticsNodes$ui_test_release(SemanticsNodeInteraction.kt:82)
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchOneOrDie(SemanticsNodeInteraction.kt:155)
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchSemanticsNode(SemanticsNodeInteraction.kt:106)
at androidx.compose.ui.test.AndroidAssertions_androidKt.checkIsDisplayed(AndroidAssertions.android.kt:29)
at androidx.compose.ui.test.AssertionsKt.assertIsDisplayed(Assertions.kt:33)
If I move my Fragment to Lifecycle.State.RESUMED
, this error is gone, which is fine for me; I don't need to test things before onResume
.
I do call setContent
on the ComposeView
in onViewCreated
, as per your example.
je...@google.com <je...@google.com> #14
when my Fragment is in Lifecycle.State.STARTED, compose will tell me this
That is by design. If you open multiple activities we only want to find Compose elements in the RESUMED activity as that is the one the user is looking at.
If there is a good reason why the test harness should look in STARTED activities (/fragments) as well, then please open a new bug and I'll take a look.
me...@thomaskeller.biz <me...@thomaskeller.biz> #15
What I also figured is that each test that does anything with composeRule
runs quite slow, i.e. each test method needs about two extra seconds to run. I looked at the profile, but cannot say for sure whats causing this. What I can see however is that a test with the Activity-based rule takes about 150 to 250ms to execute with Robolectric, when I use the empty test rule the test takes about 2100 to 2300ms. Unfortunately I couldn't reproduce it yet with a smaller example. Will further have to look into this.
me...@thomaskeller.biz <me...@thomaskeller.biz> #16
That is by design. If you open multiple activities we only want to find Compose elements in the RESUMED activity as that is the one the user is looking at.
No, this is totally fine. The error message could only a tad bit better by saying "Is your Activity or Fragment resumed?", other than that this is fine.
me...@thomaskeller.biz <me...@thomaskeller.biz> #17
I created a follow-up DialogFragment
-related crash.
ap...@google.com <ap...@google.com> #18
Branch: androidx-main
commit 0fa0145d802d8effe0e833f0fe996dbeec3ab23e
Author: Jelle Fresen <jellefresen@google.com>
Date: Thu Feb 24 14:40:50 2022
Disallow overwriting content with test content
This adds a check in AndroidComposeTest.setContent that makes sure that
the Activity on which the content is set doesn't have content yet. If
there is content already, it is a signal that the tester is probably
unintentionally doing something wrong, so help them by throwing.
Bug: 199631334
Test: ./gradlew bOS
Relnote: "`ComposeContentTestRule.setContent` will now throw an
IllegalStateException if you try to set content when there already is
content."
Change-Id: I888a5054c27d7884110415a812d0ac748be3f869
M compose/ui/ui-viewbinding/samples/src/androidTest/java/androidx/compose/ui/samples/FragmentRemoveTest.kt
M compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/BitmapCapturingTest.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
M compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/CustomActivityTest.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/accessibility/CollectionInfoTest.kt
M compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ClickTestRuleTest.kt
M compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/IsDisplayedTest.kt
M compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ActivityWithActionBar.kt
M compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/ComposeTest.android.kt
Description
Previously with plain XML UIs, one was able to test ViewModel and UI (Fragment/Activity + XML) in combination. With Compose and the
AndroidComposeTestRule
this is no longer possible:AndroidComposeTestRule
needs a test rule that provides anActivity
to the underlying compose infrastructure, while one can give it aFragmentScenarioRule
the fragment part of this cannot actually be re-used and the content of the whole Activity is replaced by the Compose UI.ComposeView.setContent()
of theFragment
(orActivity
, for that matter) isn't run when theAndroidComposeTestRule
'ssetContent
is used instead. Even worse, depending on the lifecycle state in which anActivity
is in (in which state it is started), a previous rendering could or could not have happened, leading to possible side effects with an actualViewModel
interfacing implementation.ComposeView
is not the root view of the controller component.I understand that a special
Recomposer
setup and a lot of other custom tooling is needed to make Jetpack Compose testable in an Robolectric / Android Instrumentation test, however I wish the test architecture would first and foremost provide an API to hook in / replace the existing functionality, e.g. by making it easy to inject a custom test composer in a running fragment / activity instance or by at least making this composer and possibly other utilities available for certain testing purposes.