diff --git a/dev/devicelab/lib/tasks/plugin_tests.dart b/dev/devicelab/lib/tasks/plugin_tests.dart index 21cbd824c5d0..16d9d5598661 100644 --- a/dev/devicelab/lib/tasks/plugin_tests.dart +++ b/dev/devicelab/lib/tasks/plugin_tests.dart @@ -252,36 +252,50 @@ public class $pluginClass: NSObject, FlutterPlugin { // build files. await build(buildTarget, validateNativeBuildProject: false); - if (buildTarget == 'ios') { - await testWithNewIOSSimulator('TestNativeUnitTests', (String deviceId) async { + switch(buildTarget) { + case 'apk': + if (await exec( + path.join('.', 'gradlew'), + ['testDebugUnitTest'], + workingDirectory: path.join(rootPath, 'android'), + canFail: true, + ) != 0) { + throw TaskResult.failure('Platform unit tests failed'); + } + break; + case 'ios': + await testWithNewIOSSimulator('TestNativeUnitTests', (String deviceId) async { + if (!await runXcodeTests( + platformDirectory: path.join(rootPath, 'ios'), + destination: 'id=$deviceId', + configuration: 'Debug', + testName: 'native_plugin_unit_tests_ios', + skipCodesign: true, + )) { + throw TaskResult.failure('Platform unit tests failed'); + } + }); + break; + case 'macos': if (!await runXcodeTests( - platformDirectory: path.join(rootPath, 'ios'), - destination: 'id=$deviceId', + platformDirectory: path.join(rootPath, 'macos'), + destination: 'platform=macOS', configuration: 'Debug', - testName: 'native_plugin_unit_tests_ios', + testName: 'native_plugin_unit_tests_macos', skipCodesign: true, )) { throw TaskResult.failure('Platform unit tests failed'); } - }); - } else if (buildTarget == 'macos') { - if (!await runXcodeTests( - platformDirectory: path.join(rootPath, 'macos'), - destination: 'platform=macOS', - configuration: 'Debug', - testName: 'native_plugin_unit_tests_macos', - skipCodesign: true, - )) { - throw TaskResult.failure('Platform unit tests failed'); - } - } else if (buildTarget == 'windows') { - if (await exec( - path.join(rootPath, 'build', 'windows', 'plugins', 'plugintest', 'Release', 'plugintest_plugin_test'), - [], - canFail: true, - ) != 0) { - throw TaskResult.failure('Platform unit tests failed'); - } + break; + case 'windows': + if (await exec( + path.join(rootPath, 'build', 'windows', 'plugins', 'plugintest', 'Release', 'plugintest_plugin_test'), + [], + canFail: true, + ) != 0) { + throw TaskResult.failure('Platform unit tests failed'); + } + break; } } diff --git a/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl index cc6c19a94a69..26f3272c9205 100644 --- a/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl @@ -32,4 +32,19 @@ android { defaultConfig { minSdkVersion {{minSdkVersion}} } + + dependencies { + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } } diff --git a/packages/flutter_tools/templates/plugin/android-java.tmpl/src/test/java/androidIdentifier/pluginClassTest.java.tmpl b/packages/flutter_tools/templates/plugin/android-java.tmpl/src/test/java/androidIdentifier/pluginClassTest.java.tmpl new file mode 100644 index 000000000000..9e3803f8549d --- /dev/null +++ b/packages/flutter_tools/templates/plugin/android-java.tmpl/src/test/java/androidIdentifier/pluginClassTest.java.tmpl @@ -0,0 +1,29 @@ +package {{androidIdentifier}}; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import org.junit.Test; + +/** + * This demonstrates a simple unit test of the Java portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +public class {{pluginClass}}Test { + @Test + public void onMethodCall_getPlatformVersion_returnsExpectedValue() { + {{pluginClass}} plugin = new {{pluginClass}}(); + + final MethodCall call = new MethodCall("getPlatformVersion", null); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + plugin.onMethodCall(call, mockResult); + + verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE); + } +} diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl index 4e44e629c0e0..9b35a39f3897 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl @@ -38,9 +38,27 @@ android { sourceSets { main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' } defaultConfig { minSdkVersion {{minSdkVersion}} } + + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } } diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl new file mode 100644 index 000000000000..dd0a1da7b3c7 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl @@ -0,0 +1,27 @@ +package {{androidIdentifier}} + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class {{pluginClass}}Test { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = {{pluginClass}}() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index d4b45206c2d8..2ba43bc268c2 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -251,9 +251,11 @@ "templates/plugin/android-java.tmpl/build.gradle.tmpl", "templates/plugin/android-java.tmpl/projectName_android.iml.tmpl", "templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl", + "templates/plugin/android-java.tmpl/src/test/java/androidIdentifier/pluginClassTest.java.tmpl", "templates/plugin/android-kotlin.tmpl/build.gradle.tmpl", "templates/plugin/android-kotlin.tmpl/projectName_android.iml.tmpl", "templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl", + "templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl", "templates/plugin/android.tmpl/.gitignore", "templates/plugin/android.tmpl/gradle/wrapper/gradle-wrapper.properties", "templates/plugin/android.tmpl/gradle.properties.tmpl", diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 0674b56e6230..0f285de84f2a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -2452,6 +2452,63 @@ void main() { Logger: () => logger, }); + testUsingContext('plugin includes native Kotlin unit tests', () async { + Cache.flutterRoot = '../..'; + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + await runner.run([ + 'create', + '--no-pub', + '--template=plugin', + '--org=com.example', + '--platforms=android', + projectDir.path]); + + expect(projectDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('test') + .childDirectory('kotlin') + .childDirectory('com') + .childDirectory('example') + .childDirectory('flutter_project') + .childFile('FlutterProjectPluginTest.kt'), exists); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(), + Logger: () => logger, + }); + + testUsingContext('plugin includes native Java unit tests', () async { + Cache.flutterRoot = '../..'; + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + await runner.run([ + 'create', + '--no-pub', + '--template=plugin', + '--org=com.example', + '--platforms=android', + '-a', 'java', + projectDir.path]); + + expect(projectDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('test') + .childDirectory('java') + .childDirectory('com') + .childDirectory('example') + .childDirectory('flutter_project') + .childFile('FlutterProjectPluginTest.java'), exists); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(), + Logger: () => logger, + }); + testUsingContext('plugin includes native Objective-C unit tests', () async { Cache.flutterRoot = '../..';