Flutter Engine
The Flutter Engine
FlutterEngineTest.java
Go to the documentation of this file.
1package test.io.flutter.embedding.engine;
2
3import static org.junit.Assert.assertEquals;
4import static org.junit.Assert.assertNotNull;
5import static org.junit.Assert.assertTrue;
6import static org.mockito.Mockito.any;
7import static org.mockito.Mockito.anyInt;
8import static org.mockito.Mockito.atLeast;
9import static org.mockito.Mockito.doAnswer;
10import static org.mockito.Mockito.doReturn;
11import static org.mockito.Mockito.eq;
12import static org.mockito.Mockito.mock;
13import static org.mockito.Mockito.never;
14import static org.mockito.Mockito.times;
15import static org.mockito.Mockito.verify;
16import static org.mockito.Mockito.when;
17
18import android.app.Activity;
19import android.content.Context;
20import android.content.pm.PackageManager.NameNotFoundException;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.os.LocaleList;
24import androidx.test.core.app.ApplicationProvider;
25import androidx.test.ext.junit.runners.AndroidJUnit4;
26import io.flutter.FlutterInjector;
27import io.flutter.embedding.engine.FlutterEngine;
28import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
29import io.flutter.embedding.engine.FlutterEngineGroup;
30import io.flutter.embedding.engine.FlutterJNI;
31import io.flutter.embedding.engine.loader.FlutterLoader;
32import io.flutter.embedding.engine.plugins.FlutterPlugin;
33import io.flutter.embedding.engine.plugins.PluginRegistry;
34import io.flutter.plugin.platform.PlatformViewsController;
35import io.flutter.plugins.GeneratedPluginRegistrant;
36import java.util.List;
37import org.junit.After;
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41import org.mockito.ArgumentCaptor;
42import org.mockito.Mock;
43import org.mockito.MockitoAnnotations;
44import org.mockito.invocation.InvocationOnMock;
45import org.mockito.stubbing.Answer;
46import org.robolectric.Robolectric;
47import org.robolectric.android.controller.ActivityController;
48import org.robolectric.annotation.Config;
49import org.robolectric.shadows.ShadowLog;
50
51@Config(manifest = Config.NONE)
52@RunWith(AndroidJUnit4.class)
53public class FlutterEngineTest {
54 private final Context ctx = ApplicationProvider.getApplicationContext();
55 private final Context mockContext = mock(Context.class);
57 boolean jniAttached;
58
59 @Before
60 public void setUp() {
61 MockitoAnnotations.openMocks(this);
62
63 Resources mockResources = mock(Resources.class);
64 Configuration mockConfiguration = mock(Configuration.class);
65 doReturn(mockResources).when(mockContext).getResources();
66 doReturn(mockConfiguration).when(mockResources).getConfiguration();
67 doReturn(LocaleList.getEmptyLocaleList()).when(mockConfiguration).getLocales();
68
69 jniAttached = false;
70 when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
71 doAnswer(
72 new Answer() {
73 @Override
74 public Object answer(InvocationOnMock invocation) throws Throwable {
75 jniAttached = true;
76 return null;
77 }
78 })
79 .when(flutterJNI)
80 .attachToNative();
82 }
83
84 @After
85 public void tearDown() {
87 // Make sure to not forget to remove the mock exception in the generated plugin registration
88 // mock, or everything subsequent will break.
89 GeneratedPluginRegistrant.pluginRegistrationException = null;
90 }
91
92 @Test
94 assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
95 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
96 when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
97 FlutterEngine flutterEngine = new FlutterEngine(ctx, mockFlutterLoader, flutterJNI);
98
100 assertEquals(1, registeredEngines.size());
101 assertEquals(flutterEngine, registeredEngines.get(0));
102 }
103
104 @Test
106 // Needs an activity. ApplicationContext won't work for this.
107 ActivityController<Activity> activityController = Robolectric.buildActivity(Activity.class);
108 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
109 when(mockFlutterJNI.isAttached()).thenReturn(true);
110
111 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
112 FlutterEngine flutterEngine =
113 new FlutterEngine(activityController.get(), mockFlutterLoader, mockFlutterJNI);
114
115 verify(mockFlutterJNI, times(1))
116 .updateDisplayMetrics(eq(0), any(Float.class), any(Float.class), any(Float.class));
117 }
118
119 @Test
121 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
122 when(mockFlutterJNI.isAttached()).thenReturn(true);
123
124 assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
125 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
126 when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
127 FlutterEngine flutterEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJNI);
128
129 verify(mockFlutterJNI, times(1))
130 .dispatchPlatformMessage(eq("flutter/localization"), any(), anyInt(), anyInt());
131 }
132
133 // Helps show the root cause of MissingPluginException type errors like
134 // https://github.com/flutter/flutter/issues/78625.
135 @Test
137 assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
138 GeneratedPluginRegistrant.pluginRegistrationException =
139 new RuntimeException("I'm a bug in the plugin");
140 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
141 when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
142 FlutterEngine flutterEngine = new FlutterEngine(ctx, mockFlutterLoader, flutterJNI);
143
145 // When it crashes, it doesn't end up registering anything.
146 assertEquals(0, registeredEngines.size());
147
148 // Check the logs actually says registration failed, so a subsequent MissingPluginException
149 // isn't mysterious.
150 assertTrue(
151 ShadowLog.getLogsForTag("GeneratedPluginsRegister")
152 .get(0)
153 .msg
154 .contains("Tried to automatically register plugins"));
155 assertEquals(
157 ShadowLog.getLogsForTag("GeneratedPluginsRegister").get(1).throwable.getCause());
158
159 GeneratedPluginRegistrant.pluginRegistrationException = null;
160 }
161
162 @Test
164 assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
165 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
166 when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(false);
167 new FlutterEngine(ctx, mockFlutterLoader, flutterJNI);
168
170 assertTrue(registeredEngines.isEmpty());
171 }
172
173 @Test
175 assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
176 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
177 when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
178 new FlutterEngine(
179 ctx,
180 mockFlutterLoader,
181 flutterJNI,
182 /*dartVmArgs=*/ new String[] {},
183 /*automaticallyRegisterPlugins=*/ false);
184
186 assertTrue(registeredEngines.isEmpty());
187 }
188
189 @Test
191 // Setup test.
192 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
193 when(mockFlutterJNI.isAttached()).thenReturn(true);
194
195 PlatformViewsController platformViewsController = mock(PlatformViewsController.class);
196
197 ArgumentCaptor<FlutterEngine.EngineLifecycleListener> engineLifecycleListenerArgumentCaptor =
198 ArgumentCaptor.forClass(FlutterEngine.EngineLifecycleListener.class);
199
200 // Execute behavior under test.
201 new FlutterEngine(
202 ctx,
203 mock(FlutterLoader.class),
204 mockFlutterJNI,
205 platformViewsController,
206 /*dartVmArgs=*/ new String[] {},
207 /*automaticallyRegisterPlugins=*/ false);
208
209 // Obtain the EngineLifecycleListener within FlutterEngine that was given to FlutterJNI.
210 verify(mockFlutterJNI)
211 .addEngineLifecycleListener(engineLifecycleListenerArgumentCaptor.capture());
212 FlutterEngine.EngineLifecycleListener engineLifecycleListener =
213 engineLifecycleListenerArgumentCaptor.getValue();
214 assertNotNull(engineLifecycleListener);
215
216 // Simulate a pre-engine restart, AKA hot restart.
217 engineLifecycleListener.onPreEngineRestart();
218
219 // Verify that FlutterEngine notified PlatformViewsController of the pre-engine restart,
220 // AKA hot restart.
221 verify(platformViewsController, times(1)).onPreEngineRestart();
222 }
223
224 @Test
226 PlatformViewsController platformViewsController = mock(PlatformViewsController.class);
227
228 // Execute behavior under test.
230 new FlutterEngine(
231 ctx,
232 mock(FlutterLoader.class),
233 flutterJNI,
234 platformViewsController,
235 /*dartVmArgs=*/ new String[] {},
236 /*automaticallyRegisterPlugins=*/ false);
237 verify(platformViewsController, times(1)).onAttachedToJNI();
238
239 engine.destroy();
240 verify(platformViewsController, times(1)).onDetachedFromJNI();
241 }
242
243 @Test
244 public void itUsesApplicationContext() throws NameNotFoundException {
245 Context packageContext = mock(Context.class);
246
247 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
248
249 new FlutterEngine(
250 mockContext,
251 mock(FlutterLoader.class),
252 flutterJNI,
253 /*dartVmArgs=*/ new String[] {},
254 /*automaticallyRegisterPlugins=*/ false);
255
256 verify(mockContext, atLeast(1)).getApplicationContext();
257 }
258
259 @Test
260 public void itUsesPackageContextForAssetManager() throws NameNotFoundException {
261 Context packageContext = mock(Context.class);
262 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
263
264 new FlutterEngine(
265 mockContext,
266 mock(FlutterLoader.class),
267 flutterJNI,
268 /*dartVmArgs=*/ new String[] {},
269 /*automaticallyRegisterPlugins=*/ false);
270
271 verify(packageContext, atLeast(1)).getAssets();
272 verify(mockContext, times(0)).getAssets();
273 }
274
275 @Test
276 public void itCanUseFlutterLoaderInjectionViaFlutterInjector() throws NameNotFoundException {
278 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
280 new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
281 Context packageContext = mock(Context.class);
282
283 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
284
285 new FlutterEngine(mockContext, null, flutterJNI);
286
287 verify(mockFlutterLoader, times(1)).startInitialization(any());
288 verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any());
290 }
291
292 @Test
293 public void itNotifiesListenersForDestruction() throws NameNotFoundException {
294 Context packageContext = mock(Context.class);
295
296 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
297
298 FlutterEngine engineUnderTest =
299 new FlutterEngine(
300 mockContext,
301 mock(FlutterLoader.class),
302 flutterJNI,
303 /*dartVmArgs=*/ new String[] {},
304 /*automaticallyRegisterPlugins=*/ false);
305
307 engineUnderTest.addEngineLifecycleListener(listener);
308 engineUnderTest.destroy();
309 verify(listener, times(1)).onEngineWillDestroy();
310 }
311
312 @Test
313 public void itDoesNotAttachAgainWhenBuiltWithAnAttachedJNI() throws NameNotFoundException {
314 Context packageContext = mock(Context.class);
315
316 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
317 when(flutterJNI.isAttached()).thenReturn(true);
318
319 FlutterEngine engineUnderTest =
320 new FlutterEngine(
321 mockContext,
322 mock(FlutterLoader.class),
323 flutterJNI,
324 /*dartVmArgs=*/ new String[] {},
325 /*automaticallyRegisterPlugins=*/ false);
326
327 verify(flutterJNI, never()).attachToNative();
328 }
329
330 @Test
331 public void itComesWithARunningDartExecutorIfJNIIsAlreadyAttached() throws NameNotFoundException {
332 Context packageContext = mock(Context.class);
333
334 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
335 when(flutterJNI.isAttached()).thenReturn(true);
336
337 FlutterEngine engineUnderTest =
338 new FlutterEngine(
339 mockContext,
340 mock(FlutterLoader.class),
341 flutterJNI,
342 /*dartVmArgs=*/ new String[] {},
343 /*automaticallyRegisterPlugins=*/ false);
344
345 assertTrue(engineUnderTest.getDartExecutor().isExecutingDart());
346 }
347
348 @Test
349 public void passesEngineGroupToPlugins() throws NameNotFoundException {
350 Context packageContext = mock(Context.class);
351
352 when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
353 when(flutterJNI.isAttached()).thenReturn(true);
354
355 FlutterEngineGroup mockGroup = mock(FlutterEngineGroup.class);
356
357 FlutterEngine engineUnderTest =
358 new FlutterEngine(
359 mockContext,
360 mock(FlutterLoader.class),
361 flutterJNI,
363 /*dartVmArgs=*/ new String[] {},
364 /*automaticallyRegisterPlugins=*/ false,
365 /*waitForRestorationData=*/ false,
366 mockGroup);
367
368 PluginRegistry registry = engineUnderTest.getPlugins();
369 FlutterPlugin mockPlugin = mock(FlutterPlugin.class);
370 ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
371 ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
372 registry.add(mockPlugin);
373 verify(mockPlugin).onAttachedToEngine(pluginBindingCaptor.capture());
374 assertNotNull(pluginBindingCaptor.getValue());
375 assertEquals(mockGroup, pluginBindingCaptor.getValue().getEngineGroup());
376 }
377}
static SkISize times(const SkISize &size, float factor)
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition: M44Test.cpp:18
Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader)
static void setInstance(@NonNull FlutterInjector injector)
void addEngineLifecycleListener(@NonNull EngineLifecycleListener listener)
FlutterEngine engine
Definition: main.cc:68
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530