-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Improve Geo{Point,Trace,Shape}Activity tests by adding a FakeLocationClient #2797
Closed
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…Poly to PolyFeature.
…old and new GeoPoint activity.
3 tasks
@kkrawczyk123 @mmarciniak90 Note this PR is affected by #2776; please see the note there. |
60cef8a
to
63259c2
Compare
…corded point and not the GPS location).
63259c2
to
f5e3af0
Compare
f5e3af0
to
1239815
Compare
3 tasks
1239815
to
4b6b4ee
Compare
3 tasks
This pull request is now obsolete; it has been rolled into #2865. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview
In the real app, the
LocationClient
is the component that gets GPS location readings, which doesn't exist during unit testing. So, the current tests for these activities substitute a mockLocationClient
—that means the tests poke at the internal methods of the activity and verify that it called specific methods on theLocationClient
.This PR changes the tests to use a fake
LocationClient
instead—that means we supply an object that actually behaves like a realLocationClient
, operate the activity through its UI and external interface, and verify that the activity behaved properly.Guidance to reviewers
Please note that this PR is based on #2776, not on master. To see the relevant diff, look only at the last commit: 63259c2
A good place to start is the
FakeLocationClient
—it's a simple implementation of theLocationClient
interface, with a few extra methods (likereceiveFix
) to tell it how to behave. Then the tests should be fairly straightforward to understand. The changes inGeoPointMapActivity
expose some of the string formatting functions so that the test isn't dependent on precise formatting.What has been done to verify that this works as intended?
These tests all pass, and test coverage numbers (as measured by IntelliJ) are all greater than or equal to the previous coverage numbers for every class related to this change.
Why is this the best possible solution? Were any other approaches considered?
Fakes are usually better than mocks. Where feasible, using fakes leads to better tests; mocks should only be used as a last resort.
Mocking verifies the implementation. It only spot-checks that particular methods were called, so the test conditions are less realistic and only small bits of the code are exercised. Moreover, mock-style tests are fragile and will break if the implementation changes, because they depend both on the internal entry points invoked by the test as well as the specific methods invoked by the activity. Another way of saying this is that mocking violates encapsulation.
Faking verifies the behaviour. More of the unit-under-test gets exercised in a realistic way. When the test passes, it gives us more confidence that the unit-under-test actually behaved correctly. And if we refactor the unit-under-test in a way that doesn't change its public interface, the test doesn't break.
For an example, open the diff and have a look at the last dozen lines of
GeoShapeActivityTest
. The mocking-style testactivityShouldShowErrorDialogOnClientError
is on the left. The new testshouldShowErrorDialogIfLocationClientFails
is on the right.The mocking-style test on the left (a) invokes
map.onClientStartFailure()
and then (b) checks that the alert dialog is showing. But what doesmap.onClientStartFailure()
do? Its body is just one line:So the test mostly just confirms that this method contains this one line—which is obvious from looking at the code and hardly worth testing. An activity could have no interaction with
LocationClient
—no location functionality at all—and still pass this test!In contrast, the new test on the right (a) sets up a
FakeLocationClient
that will simulate a failure, then (b) starts the activity, then (c) checks that the alert dialog is showing.In order to pass this test, the activity must use the
LocationClient
in a way that would catch its failure in real life—in the course of starting up, the activity must at some point cause aLocationClient
to be created, set up the appropriate listener to catch the failure event, and start theLocationClient
so as to trigger the failure event.How does this change affect users?
This PR only touches the unit tests; it has no user-visible effect.
Does this change require updates to documentation?
No.
Before submitting this PR, please make sure you have:
./gradlew checkAll
and confirmed all checks still pass OR confirm CircleCI build passes and run./gradlew connectedDebugAndroidTest
locally.