-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Let first frame listeners self remove #11014
Conversation
private static class FlutterJNITest extends FlutterJNI { | ||
protected void onFirstFrame() { | ||
// Exposed here for tests. | ||
super.onFirstFrame(); |
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.
Why is this here?
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.
Discussed offline. Probably won't need this class once I move the package up one level at which point it'll be in the same package as the FlutterJNI.
@Before | ||
public void setUp() { | ||
jniUnderTest = new FlutterJNITest(); | ||
rendererUnderTest = new FlutterRenderer(jniUnderTest); |
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 recommend instantiating the object under test within each test. You never know when a test is going to be introduced that requires some kind of unusual instantiation, and when it is, that developer will be forced to change every other test to make that happen. Better to just let each test configure the object under test as it desired.
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.
Also, I don't think we need to suffix things with "test"...everything in here is related to a test.
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 the first point was to not use test class setup/teardowns? It's probably a bit philosophical but I think it'll be efficient to just use industry standard practices (such as
cs/@before$ file:test.java
) - I think this case is a bit exceptional but most of the time, there'd be things that are under test and a bunch more things that are mocks supporting those real classes under test. This makes it clear which are actors in the test and which aren't (e.g.
cs/"undertest =" file:test.java
) but I don't feel strongly about it.
@Test | ||
public void firstFrameListenerCanRemoveThemselves() { | ||
AtomicInteger callbackCalled = new AtomicInteger(0); | ||
OnFirstFrameRenderedListener callback = new OnFirstFrameRenderedListener() { |
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 can probably accomplish this with a mock with less code and complexity than what you have here...
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.
mockito is unfortunately a bit less capable for these level triggered stuff (mostly due to java)
An equivalent here will turn into
@Mock OnFirstFrameRenderedListener callback;
when(callback. onFirstFrameRendered()).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
rendererUnderTest.removeOnFirstFrameRenderedListener(callback);
}
});
which is even less directly readable.
} | ||
|
||
@Test | ||
public void firstFrameListenerCanRemoveThemselves() { |
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'd like to follow a policy whereby any true unit test prefixes the test with "it", e.g., "itAllowsFirstFrameListenersToRemoveThemselves". This pattern will help ensure that unit tests really are unit tests by grammatically framing in terms of the object under test.
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.
Also, this isn't really testing the broader capability of removing oneself as a listener. This is specifically testing the capabilityof removing oneself during a first frame callback. You may want to make that explicit in the test name.
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.
ok. Renamed.
I think the only way to not remove themselves during the first frame would be something like itAllowsFirstFrameListenersToRemoveThemselvesWithoutUsingAHandlerToPostBackADeferredRemoval :D
How's itAllowsFirstFrameListenersToRemoveThemselvesInline?
}; | ||
|
||
rendererUnderTest.addOnFirstFrameRenderedListener(callback); | ||
jniUnderTest.onFirstFrame(); |
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 would recommend that in all tests we clearly demarcate between setup/execution/verification. In this case everything up to and including the addOnFirstFrameRenderedListener
is part of the setup, the onFirstFrame()
invocation is the execution, and everything that follows is verification.
In addition to placing a blank line and maybe a comment between these 2 lines above, you could also slightly refactor what comes below. I dont think you actually need to interleave 2 onFirstFrame()
s and 2 assert
s. You can simply invoke onFirstFrame()
twice in a row, and then verify that the callback was invoked exactly 1 time.
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 it's still worth checking for the actual timing (e.g. that we don't have false positives on onFirstFrame only calling listeners on second invocation or something)
ensureRunningOnMainThread(); | ||
if (renderSurface != null) { | ||
renderSurface.onFirstFrameRendered(); | ||
} | ||
// TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) | ||
|
||
for (OnFirstFrameRenderedListener listener : firstFrameListeners) { | ||
CopyOnWriteArrayList<OnFirstFrameRenderedListener> mutableListeners = |
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.
This leads me to believe that this is not a test of FlutterRenderer
but rather a test of FlutterJNI
. I think something needs to change in this PR to achieve correctness in that regard. If you're truly unit testing FlutterRenderer
then you shouldn't have to make this protected. If you're testing FlutterJNI
then the test suite should be switched accordingly.
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 there's a couple of layers of testings we'll need either way.
- I think we'll need strict unit tests. Where we're testing classes. I added a FlutterJNITest for this one.
- Some of the classes are just not part of the public API are not going to be used. We should have medium tests that tests things between an API surface and some sort of abstraction layer. In this case, I think making things that platform_view_android_jni.cc calls also test callable probably makes sense.
- We need integration tests that also includes C++ stuff but since those are heavier, they won't be per feature.
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
@Config(manifest=Config.NONE) | ||
@RunWith(RobolectricTestRunner.class) |
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.
Why does this need to be robolectric?
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.
FlutterRenderer imports android classes
037b7e3
to
a9a9247
Compare
cc @RedBrogdon since you wanted to follow along |
I think we can close this PR due to related changes made here: |
yup, thanks for re-doing all the tests |
See test:
Let the listener callbacks remove themselves for a one-time callback without java.util.ConcurrentModificationException.