Jul 28 | 8:20 AM |
Mark M. | has entered the room |
Jul 28 | 8:25 AM |
Mark M. | turned on guest access |
Jul 28 | 8:30 AM |
Marek D. | has entered the room |
Mark M. |
hello, Mark!
|
Mark M. |
er, Marek!
|
Mark M. |
how can I help you today?
|
Marek D. |
Hi Mark!
|
Marek D. |
my questions are not that well prepared so I will improvise
|
Mark M. |
my answers are even less prepared! :-)
|
Marek D. |
well sometimes key to the answer is well stated question :)
|
Marek D. |
or correct search phrase in google
|
Marek D. |
ok we decided to use flows
|
Marek D. |
kotlin flows
|
Marek D. |
so i.e. ViewModel has some repository
|
Marek D. |
that exposes a flow
|
Marek D. |
for example I want to have a flow of booleans that location services are enabled
|
Jul 28 | 8:35 AM |
Marek D. |
and ask user to turn them on if not
|
Marek D. |
I uses callbackFlow
|
Marek D. |
where I register broadcast receiver to know if the user turn them off
|
Marek D. |
now I am thinking how can I test this in unit test
|
Marek D. |
let's say I start with location enabled and user disables it
|
Marek D. |
so in real app I would get true, then broadcast receiver then false in collector
|
Marek D. |
in test I imagine
|
Marek D. |
I need to collect 1st one
|
Marek D. |
manually trigger broadcast receiver
|
Marek D. |
and collect 2nd event
|
Marek D. |
there is flow.first()
|
Marek D. |
but not the second()
|
Marek D. |
also subsequent call to first() is not exactly what is needed
|
Mark M. |
let's back up a step: are you looking to unit test the repository or the viewmodel here?
|
Marek D. |
I think repository
|
Mark M. |
OK
|
Marek D. |
so I want to make sure that turning location off emits next event
|
Mark M. |
so, there are two problems: how do you set up the appropriate mocks/fakes to get the repository to do what you want in the test, and how do you assert the results coming out of the Flow
|
Jul 28 | 8:40 AM |
Mark M. |
right now, I don't think there is a TestObserver equivalent for Flow
|
Mark M. |
my main work right now is still using RxJava, not coroutines, so I have not written many Flow tests
|
Mark M. |
IIRC, what I did was collect() the Flow into a MutableList, then assert the contents of the MutableList at various points in the test
|
Mark M. | |
Mark M. |
in this case, it is an instrumented test, not a unit test, but the same principle holds
|
Mark M. |
I do not know if this is "state of the art" for testing Flow, though my guess is that it is not
|
Mark M. |
but, it worked! :-)
|
Mark M. |
my guess/hope is that, at some point, there will be a dedicated test class for testing the output of a Flow
|
Marek D. |
cancelling the collector jobs feels inconvenien
|
Jul 28 | 8:45 AM |
Mark M. |
yes, hopefully there is a better approach that I just have not encountered yet
|
Mark M. |
if you fail to cancel it, though, you get an error
|
Marek D. |
from runBlockingTest
|
Mark M. |
yes
|
Marek D. |
allright will take a look
|
Marek D. |
another question
|
Marek D. |
if you need location
|
Marek D. |
there is a permission thing
|
Marek D. |
to check you neeed to context
|
Marek D. |
you need context
|
Jul 28 | 8:50 AM |
Marek D. |
to request you need activity/fragment
|
Marek D. |
so let's say I click on some button that says enable permission in my UI
|
Kumar V. | has entered the room |
Kumar V. |
Hello everyone
|
Marek D. |
so you usually forward this to VM
|
Mark M. |
(hello, Kumar! I will be with you shortly!)
|
Marek D. |
now you need activity to reuqest permission
|
Kumar V. |
Sure Mark. No issues
|
Marek D. |
and contex to see if you already requested them
|
Marek D. |
sorry activity probably (permission rationale)
|
Marek D. |
so should I pass activity to VM to do this
|
Marek D. |
or should I create some events in VM and listen to them in View
|
Mark M. |
IMHO, no
|
Mark M. |
events or states, emitted by the VM, is a better choice
|
Marek D. |
so I emit event, please request a permission ?
|
Marek D. |
and activity does this
|
Mark M. |
well, that starts to get into the details of your approach between your VM and your activity/fragment, and there are a lot of possibilities
|
Mark M. |
but yes, emitting a event to cause the activity to show the permission dialog is one likely option
|
Jul 28 | 8:55 AM |
Marek D. |
recently I created an interface with bunch of methods in activity
|
Marek D. |
so I don't pass activity but that interface
|
Marek D. |
and all if(s) are in the VM
|
Mark M. |
that is at least better than passing the activity itself, insofar as it is easier for you to write tests
|
Marek D. |
and is easy to mock this interface
|
Mark M. |
right
|
Mark M. |
personally, having the VM make direct calls on an activity/fragment, even via an interface, makes me nervous
|
Mark M. |
mostly, it's a memory leak issue
|
Marek D. |
if all calls are synchronous ?
|
Mark M. |
it would be easy for somebody to accidentally hold onto that interface implementation somewhere that would survive the configuration change
|
Mark M. |
today, right now, you might be thinking of that and avoid making that mistake
|
Mark M. |
my concern is more of tomorrow, and the next day, and the day after that
|
Marek D. |
so how to prevent it ?
|
Mark M. |
don't pass an activity/fragment to a VM, even via an interface
|
Mark M. |
do something else, where the VM is emitting states and/or events
|
Marek D. |
but then I rely on my view
|
Marek D. |
to pass me the information
|
Mark M. |
yes, just as you rely on the view to pass UI events ("the user clicked this button", etc.)
|
Mark M. |
I am not saying that your interface approach will not work -- I just would not be super happy with that technique
|
Marek D. |
well I feel more comfortable If I can test longer flow(s)
|
Jul 28 | 9:00 AM |
Marek D. |
because in that event scenarios
|
Marek D. |
the functionality is achieved because a chain of events happen
|
Mark M. |
let me take a question from Kumar, and I will return to you shortly
|
Mark M. |
Kumar: hi! how can I help you today?
|
Marek D. |
go ahead
|
Kumar V. |
View paste
|
Mark M. |
what specifically are you seeing with WorkManager?
|
Kumar V. |
PeriodicWorkRequest doesn't seem to run at all.
|
Mark M. |
does it work if you choose a somewhat shorter period (e.g., 10 minutes instead of 1 day)?
|
Mark M. |
because I would expect PeriodicWorkRequest to work in general
|
Jul 28 | 9:05 AM |
Kumar V. |
No, actually the minimum time for the PeriodicWorkRequest is 15min. I set that, but still it didn't work.
|
Mark M. |
then my guess is that there is something wrong with how you are using it
|
Mark M. |
there certainly can be bugs in the Jetpack libraries
|
Mark M. |
but I would be very surprised if PeriodicWorkRequest simply failed 100% of the time
|
Kumar V. |
Ok, on a general note What would you suggest ? Should I go for WorkManager or AlarmManager
|
Kumar V. |
but I would be very surprised if PeriodicWorkRequest simply failed 100% of the time. --> It did fail, I had the app the in the background as well.
|
Mark M. |
if the work needs to be done once in a 24-hour window, and you do not care where in that window the work gets done, use WorkManager
|
Mark M. |
if you need to try for greater control over the precise timing, you can try AlarmManager, or have your server send a high-priority FCM message
|
Mark M. |
to be honest, periodic background work is a mess on modern versions of Android, and I try to steer developers away from having to rely upon it
|
Kumar V. |
Ok. It is a simple thing to configure but it didn't work. Let me do more research and try. Will let you know. I have one last question
|
Mark M. |
Marek already had two questions, so... go ahead with your second question!
|
Kumar V. |
View paste
|
Mark M. |
you filed an SO bump request for that 20 minutes ago, I think
|
Kumar V. |
yes.
|
Mark M. |
and I noticed the question yesterday on Slack and upvoted it
|
Mark M. |
I do not know what is going on there
|
Mark M. |
you might want to edit the question and provide a stack trace, if you have one
|
Kumar V. |
Ok, don't know how it came in the slack.
|
Jul 28 | 9:10 AM |
Mark M. |
sorry, I mean Stack Overflow
|
Mark M. |
(too many communication channels, too little time...)
|
Kumar V. |
you might want to edit the question and provide a stack trace, if you have one --> Will add more
|
Kumar V. |
Ha ha, Ok.
|
Mark M. |
off the cuff, this feels like a manufacturer problem, as there should be a primary external storage volume
|
Kumar V. |
Ok. Guess more people are to encounter it soon once they make targetSdk change to 29.
|
Mark M. |
I can put a bounty on the question when SO lets me
|
Mark M. |
but, if you can add the full stack trace, that will help get you an answer
|
Kumar V. |
Ok, will do that.
|
Mark M. |
if I get time later this week, I may try some experiments with some Moto devices and see if I can reproduce the problem
|
Kumar V. |
Ok. That's it Mark. Won't take more of your time. It was nice chatting with you. Would love to discuss more in the future. Thanks a lot.
|
Mark M. |
I have three of these chats a week (for most weeks), so join which ones you can!
|
Jul 28 | 9:15 AM |
Mark M. |
also, there is the Android Developer Discuss board that you can join: https://community.commonsware.com/t/welcome-to-...
|
Mark M. |
in the meantime...
|
Mark M. |
Marek: back to you! do you have another question?
|
Kumar V. |
Sure. Will check.
|
Marek D. |
yes, unprepared one :)
|
Marek D. |
is robolectric still a thing?
|
Mark M. |
I am under the impression that it is getting folded into the Jetpack as part of Project Nitrogen, but I have heard very little about this in the past 15 months or so
|
Mark M. |
personally, I have not been a big Robolectric fan -- I'd prefer to run instrumented tests for things that have a strong tie to the Android SDK
|
Mark M. |
but, I tend to work on smaller projects, where that is practical
|
Mark M. |
if you have 10,000 tests, you cannot afford for too many of them to be instrumented tests instead of unit tests
|
Marek D. |
well robolectric is not that fast either
|
Marek D. |
ofc still faster than instrumentation
|
Mark M. |
when I need to unit test stuff that has Android SDK ties, I tend to just roll custom mocks with Mockito
|
Mark M. |
that may not be the best, but it gets the job done and makes my clients happy
|
Jul 28 | 9:20 AM |
Mark M. |
and I like happy clients :-)
|
Jul 28 | 9:20 AM |
Marek D. |
I am still the only one so I keep asking
|
Marek D. |
I am testing simple thing
|
Marek D. |
I have extension function on my context
|
Marek D. |
to get location permission status
|
Marek D. |
and that is passed up to some other layers
|
Marek D. |
so you can have permission or not
|
Marek D. |
true false
|
Marek D. |
I am writing two tests
|
Marek D. |
but they look the same
|
Marek D. |
just the value differs
|
Marek D. |
there is parametrized test annotation
|
Marek D. |
I can do this
|
Marek D. |
but sometimes I add random boolean
|
Marek D. |
so I write one test
|
Marek D. |
some people say is bad to have random test
|
Marek D. |
or I have a test
|
Marek D. |
that I think value of one parameter is not important
|
Marek D. |
so I also like to give it random value
|
Jul 28 | 9:25 AM |
Marek D. |
if it breaks then my assumption of uniportance breaks as well
|
Marek D. |
but that's a good sign :)
|
Marek D. |
is this really bad approach
|
Mark M. |
the downside of the random-value approach is that your test may not fail 100% of the time
|
Mark M. |
it will fail some percentage of the time, depending on when the random value happens to trigger a failure scenario
|
Mark M. |
testing purists would argue that you need to cover the range of inputs so that you always test the failure scenario
|
Mark M. |
from a more practical standpoint, it may mean that your test fails in somebody else's work
|
Mark M. |
and that somebody may be irritated that your random test failed their CI run or something
|
Mark M. |
so, for example, I was on a project that used Geocoder to get a street address for a GPS coordinate
|
Mark M. |
we found that Geocoder would sometimes change the results it returned, in ways that broke the app (related to address parsing)
|
Mark M. |
I wrote some instrumented tests specifically to try to detect when Google's servers behind Geocoder would change their output format
|
Mark M. |
but this meant that, all of a sudden, tests would fail, having nothing to do with the work other developers were doing
|
Mark M. |
those developers were not very happy with me, even though my tests were doing exactly what we needed them to do
|
Mark M. |
we wound up using @FlakyTest or something to get them out of the CI test runs and only in manual runs
|
Jul 28 | 9:30 AM |
Mark M. |
that's the sort of risk that you take with random values, though in your case it will not be quite as bad as mine
|
Marek D. |
but you found a real case scenario failure
|
Mark M. |
yes, but at a random time and usually affecting all developers, until either we fixed the test or Google changed their servers
|
Mark M. |
in theory, we want tests to say when the code is not working
|
Mark M. |
in practice, we want tests to say when *our changes* to the code is not working
|
Mark M. |
and random inputs, whether directly from test code or, in my case, based on server responses that we don't control, break the "our changes" assumption
|
Mark M. |
the fact that it makes the *app* better may not help make the *team* better
|
Mark M. |
and both have to be taken into account
|
Mark M. |
and with that, I need to wrap up today's chat
|
Mark M. |
the transcript will be posted to https://commonsware.com/office-hours/ shortly
|
Mark M. |
the next chat is Thursday in the 7:30pm US Eastern time slot
|
Mark M. |
have a pleasant day!
|
Marek D. |
bye bye
|
Marek D. | has left the room |
Kumar V. | has left the room |
Mark M. | turned off guest access |