diff --git a/Sources/SkipSyntax/Kotlin/KotlinUnitTestTransformer.swift b/Sources/SkipSyntax/Kotlin/KotlinUnitTestTransformer.swift index de9b59cd..afb603fa 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinUnitTestTransformer.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinUnitTestTransformer.swift @@ -19,8 +19,6 @@ final class KotlinUnitTestTransformer: KotlinTransformer { /// Types annotated with `@Suite` in Swift source. private var swiftTestingSuites: [Source.FilePath: Set] = [:] - static let testRunnerAnnotation: String? = nil // was: "@org.junit.runner.RunWith(androidx.test.ext.junit.runners.AndroidJUnit4::class)" - func gather(from syntaxTree: SyntaxTree) { var testFunctions: Set = [] var suiteTypes: Set = [] @@ -88,11 +86,7 @@ final class KotlinUnitTestTransformer: KotlinTransformer { } else { functionDeclaration.annotations += ["@Test"] } - if let testRunnerAnnotation = Self.testRunnerAnnotation { - if !owningClass.annotations.contains(testRunnerAnnotation) { - owningClass.annotations += [testRunnerAnnotation] - } - } + owningClass.addTestRunnerAnnotation() // For Swift Testing @Suite types that don't extend XCTestCase, // make them implement the XCTestCase interface for assertion access if isSwiftTesting && !isXCTest { @@ -141,9 +135,7 @@ final class KotlinUnitTestTransformer: KotlinTransformer { let classDeclaration = KotlinClassDeclaration(name: className, signature: .named(className, []), declarationType: .classDeclaration) classDeclaration.modifiers = Modifiers(isFinal: true) classDeclaration.inherits = [.named("XCTestCase", [])] - if let testRunnerAnnotation = Self.testRunnerAnnotation { - classDeclaration.annotations = [testRunnerAnnotation] - } + classDeclaration.addTestRunnerAnnotation() classDeclaration.extras = functionDeclaration.extras // Move the function into the class @@ -215,3 +207,23 @@ final class KotlinUnitTestTransformer: KotlinTransformer { return infos.contains { $0.inherits.contains { $0.isNamed("XCTestCase", moduleName: "XCTest", generics: []) } } } } + +extension KotlinClassDeclaration { + /// A default annotation to add to generated test cases, which is required by Robolectric to correcly mock various Android API + /// + /// Failure to include this will result in errors like: + /// ``` + /// java.lang.RuntimeException: Method parse in android.net.Uri not mocked. + /// ``` + /// See also: https://developer.android.com/training/testing/local-tests#mocking-dependencies + static let testRunnerAnnotation: String? = "@org.junit.runner.RunWith(androidx.test.ext.junit.runners.AndroidJUnit4::class)" + + func addTestRunnerAnnotation() { + if let testRunnerAnnotation = Self.testRunnerAnnotation { + // only add the annotation of the class itself has not already specified an annotation + if !self.annotations.contains(where: { $0.hasPrefix("@org.junit.runner.RunWith(") || $0.hasPrefix("@RunWith(") }) { + self.annotations += [testRunnerAnnotation] + } + } + } +} diff --git a/Tests/SkipSyntaxTests/TransformerTests.swift b/Tests/SkipSyntaxTests/TransformerTests.swift index f7b87ec9..da42e005 100644 --- a/Tests/SkipSyntaxTests/TransformerTests.swift +++ b/Tests/SkipSyntaxTests/TransformerTests.swift @@ -3,6 +3,9 @@ // SPDX-License-Identifier: AGPL-3.0-only import XCTest +@testable import SkipSyntax + +let testRunnerAnnotation: String = KotlinClassDeclaration.testRunnerAnnotation?.appending("\n") ?? "" final class TransformerTests: XCTestCase { func testUnitTestTransformer() async throws { @@ -22,7 +25,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal open class TestCase: XCTestCase { + \(testRunnerAnnotation)internal open class TestCase: XCTestCase { @Test internal open fun testSomeTest() = Unit @@ -52,7 +55,7 @@ final class TransformerTests: XCTestCase { import skip.unit.* - internal open class TestCase: XCTestCase { + \(testRunnerAnnotation)internal open class TestCase: XCTestCase { @OptIn(ExperimentalCoroutinesApi::class) @Test @@ -86,7 +89,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class MyTests: XCTestCase { + \(testRunnerAnnotation)internal class MyTests: XCTestCase { @Test internal fun addition(): Unit = expectEqual(1 + 1, 2) } @@ -106,7 +109,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class MyTests: XCTestCase { + \(testRunnerAnnotation)internal class MyTests: XCTestCase { @Test internal fun boolCheck() { val x = true @@ -128,7 +131,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class MyTests: XCTestCase { + \(testRunnerAnnotation)internal class MyTests: XCTestCase { @Test internal fun inequality(): Unit = expectNotEqual(1, 2) } @@ -148,7 +151,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class MyTests: XCTestCase { + \(testRunnerAnnotation)internal class MyTests: XCTestCase { @Test internal fun unwrap() { val x: Int? = 42 @@ -178,7 +181,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class MathTests: XCTestCase { + \(testRunnerAnnotation)internal class MathTests: XCTestCase { @Test internal fun addition(): Unit = expectEqual(2 + 2, 4) @@ -205,7 +208,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class CompTests: XCTestCase { + \(testRunnerAnnotation)internal class CompTests: XCTestCase { @Test internal fun comparisons() { expectGreaterThan(5, 3) @@ -227,7 +230,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class AdditionTests: XCTestCase { + \(testRunnerAnnotation)internal class AdditionTests: XCTestCase { @Test internal fun addition(): Unit = expectEqual(1 + 2, 3) } @@ -252,12 +255,12 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class AdditionTests: XCTestCase { + \(testRunnerAnnotation)internal class AdditionTests: XCTestCase { @Test internal fun addition(): Unit = expectEqual(1 + 1, 2) } - internal class SubtractionTests: XCTestCase { + \(testRunnerAnnotation)internal class SubtractionTests: XCTestCase { @Test internal fun subtraction(): Unit = expectEqual(5 - 3, 2) } @@ -277,7 +280,7 @@ final class TransformerTests: XCTestCase { """, kotlin: """ import skip.unit.* - internal class BoolCheckTests: XCTestCase { + \(testRunnerAnnotation)internal class BoolCheckTests: XCTestCase { @Test internal fun boolCheck() { val x = true diff --git a/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift b/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift index 2b1ae561..92c13f6f 100644 --- a/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift +++ b/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift @@ -294,7 +294,7 @@ extension XCTestCase { ].compactMap({ $0 }) do { - let result = try await Process.checkNonZeroExit(arguments: args, environment: env, loggingHandler: { msg in + let result = try await Process.checkNonZeroExit(arguments: args, environmentBlock: .init(env), loggingHandler: { msg in print("kotlinc> " + msg) })