{"id":28435180,"url":"https://github.com/skiptools/skip-unit","last_synced_at":"2026-04-07T20:01:29.155Z","repository":{"id":189262305,"uuid":"680361079","full_name":"skiptools/skip-unit","owner":"skiptools","description":"Unit testing for Skip apps, adapting Swift XCUnit to transpiled Kotlin JUnit test cases","archived":false,"fork":false,"pushed_at":"2026-03-29T21:46:25.000Z","size":336,"stargazers_count":2,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-30T00:46:12.388Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://skip.dev/docs/modules/skip-unit/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skiptools.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://skip.dev/sponsor"]}},"created_at":"2023-08-19T02:15:19.000Z","updated_at":"2026-03-29T21:46:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"6eac43be-e2a5-42d3-93d1-2e208d5a4110","html_url":"https://github.com/skiptools/skip-unit","commit_stats":null,"previous_names":["skiptools/skip-unit"],"tags_count":112,"template":false,"template_full_name":null,"purl":"pkg:github/skiptools/skip-unit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-unit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-unit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-unit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-unit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skiptools","download_url":"https://codeload.github.com/skiptools/skip-unit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-unit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31526666,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-05T20:06:52.445Z","updated_at":"2026-04-07T20:01:29.150Z","avatar_url":"https://github.com/skiptools.png","language":"Kotlin","funding_links":["https://skip.dev/sponsor"],"categories":[],"sub_categories":[],"readme":"# SkipUnit\n\nBase Skip gradle plugin module and unit testing for [Skip](https://skip.dev) apps, adapting Swift XCUnit to transpiled Kotlin JUnit test cases.\n\n## About\n\nSkipUnit vends the `skip.unit` Kotlin package containing a Swift `XCTest` interface to the Java/Kotlin `JUnit` testing framework.\nCombined with the Skip [transpiler](https://source.skip.dev/skip), this provides automatic transpilation of XCUnit test cases as JUnit tests, which enables parity testing to identify and isolate any differences between your Swift code and your transpiled Skip Kotlin code.\n\n## Dependencies\n\nSkipUnit depends on the [skip](https://source.skip.dev/skip) transpiler plugin and has no additional library dependencies.\n\nIt is part of the *Skip Core Frameworks* and is not intended to be imported directly.\nThe module is transparently adopted through the translation of `import XCUnit` into `import skip.unit.*` by the Skip transpiler.\n\n## Parity Testing\n\nParity testing is a central aspect of Skip development. It ensures that your Swift and Kotlin behave exactly the same.\n\nYou do not need to import any Skip-specific libraries or APIs in order to perform parity testing. \nThe standard modules of `XCTest` and `Foundation`, as well as your own library dependencies, will be sufficient for performing most test operations.\n\nYour tests **do** need to run against both the macOS and iOS platforms, because the Skip tests are only executed when running on the macOS destination. See [Running Tests from Xcode](#running-tests-from-xcode).\n\n## Testing Modes\n\nWhen writing Swift code that doesn't need access to device-specific services or other iOS-specific capabilities, the fastest development path is to build and run against macOS. Most common APIs behave the same between macOS and iOS, and running tests locally against the macOS version of an app enables quick unit testing cycles, both when developing with Xcode interactively, as well as running as part of a continuous integration (CI) process by running `swift test`. When tests need iOS-specific functionality, those tests can be gated inside `#if os(iOS)` blocks on order to enable them to be run only when targeting the iOS Simulator or device.\n\nSimilarly, when running transpiled Kotlin/Gradle tests, the fastest development mode is to run the JUnit tests locally without launching an Android emulator or connecting to a device. While the Java and Kotlin APIs that underly `SkipFoundation` are sufficient for many testing needs, there are some Android-specific APIs that are needed. This compatibility is provided by the Robolectric framework, which provides a set of Android-compatible APIs for app testing. \n\nKeep in mind that *Robolectric is not Android*. The local Robolectric testing environment is similar, but not identical, to an actual Android emulator or device. Some Android APIs are missing from Robolectric, and it is possible that the compiled Java bytecode run locally differs from the Dalvik/ART byte code that is run in a true Android environment. If this becomes a problem, follow the instructions below to run the tests on an Android device or emulator instead.\n\nThese six modes of testing can be summarized by the following table:\n\n|          | Swift              | Kotlin           | Fidelity | Speed |\n| ---------|--------------------|------------------|----------|-------|\n| Local    | macOS w/ Cocoa API | macOS w/ Robolectric API | lowest | highest |\n| Simula   | iOS Simualtor      | Android Emulator | good | slower |\n| Actual   | iOS Device         | Android Device   | highest | slowest |\n\n\nBy default, Skip uses the simulated Robolectric Android environment to run your transpiled tests. To run them against an Android device or emulator instead, set the device or emulator ID in the `ANDROID_SERIAL` environment variable. This can be done either in the Xcode scheme's `Run` action arguments for the target, or as a standard environment variable when using the command line:\n\n```shell\n\u003e ANDROID_SERIAL=emulator-5554 swift test\n```\n\n## Writing Tests\n\nSkip Lite supports both XCTest and a subset of Swift Testing for writing transpiled tests.\n\n### XCTest\n\nA standard Skip test using XCTest:\n\n```swift\nimport XCTest\n\nfinal class MyUnitTests: XCTestCase {\n    func testSomeLogic() throws {\n        XCTAssertEqual(1 + 2, 3, \"basic test\")\n    }\n}\n```\n\n### Swift Testing\n\nYou can also write tests using Swift Testing's `@Test` and `@Suite` annotations. The `#expect` macro maps to assertion functions and `#require` maps to `requireNotNil`:\n\n```swift\nimport Testing\n\n@Suite struct MathTests {\n    @Test func addition() {\n        #expect(1 + 1 == 2)\n    }\n\n    @Test func inequality() {\n        #expect(3 != 5)\n    }\n}\n```\n\nFreestanding `@Test` functions (not inside a struct or class) are also supported and are automatically wrapped in a generated test class for JUnit:\n\n```swift\nimport Testing\n\n@Test func addition() {\n    #expect(1 + 2 == 3)\n}\n```\n\nThe following Swift Testing features are supported:\n- `@Test` functions (both as members and freestanding)\n- `@Suite` types\n- `#expect` with equality (`==`), inequality (`!=`), comparisons (`\u003e`, `\u003c`, `\u003e=`, `\u003c=`), and boolean expressions\n- `#require` for optional unwrapping\n\nNot all Swift Testing features are currently transpiled. Parameterized tests, traits, and tags are not supported.\n\n**Note**: The Skip transpiler currently does not have access to the internal API of the module being tested. If you take advantage of Swift's `@testable imports` to exercise internal API, the transpiler will not be able to perform its usual type inference when translating your test code. This just means that you might have to be more explicit about types and to fully-qualify values (e.g. `MyType.value` instead of just `.value`) when unit testing internal API.\n\n## Running Tests\n\nThe transpiled unit tests are intended to be run as part of the standard Xcode and Swift Package Manager testing process.\n\nThe tests are driven by a test harness file called `XCSkipTests.swift`. If your test target does not already contain a file with this name, the Skip build plugin will auto-generate one during the build. This means that for most projects, you do not need to create or maintain this file yourself — just run `swift test` and the harness is created for you.\n\nIf you need to customize the test harness (for example, to specify a device target or adjust the Gradle task), you can add your own `XCSkipTests.swift` to your test target and the build plugin will use it instead of generating one:\n\n```\n#if os(macOS) // Skip transpiled tests only run on macOS targets\nimport SkipTest\n\n/// This test case will run the transpiled tests for the Skip module.\n@available(macOS 13, *)\nfinal class XCSkipTests: XCTestCase, XCGradleHarness {\n    public func testSkipModule() async throws {\n        try await runGradleTests(device: .none) // set device ID to run in Android emulator vs. Robolectric\n    }\n}\n#endif\n```\n\n### Running Tests from Xcode\n\nThe transpiled test cases will automatically run whenever testing against the **macOS** run destination.\nAs such, you need to ensure that your Swift code compiles and runs the same on macOS and iOS.\nThis is a pre-requisite for Skip's parity testing, which runs the XCUnit test cases on macOS against the transpiled Kotlin tests in the Android testing environment. While many of the Foundation and SwiftUI APIs are identical on macOS and iOS, you may occasionally have to work around minor differences. \n\nThe transpiled unit tests are run by forking the `gradle test` process on the macOS host machine against the output folder of the skip transpiler plugin.\nThe JUnit test output XML files are then parsed, and a report summarizing the test results is presented.\n\n### Running Tests from the CLI/CI\n\nThe `swift test` command on macOS will automatically perform test transpliation.\nThis can be used for headless testing locally as well as on a continuous integration (CI) server.\n\nNote that running test cases will also initiate a Gradle build, which has the side-effect of Gradle downloading all the library dependencies for the modules. When tests depend on frameworks like `SkipUI`, which depends on many Jetpack Compose libraries, the dependencies can amount to over 1 gigabyte in the `~/.gradle/` folder. \n\nThis may lead to a slow initial run of the tests and a perception that the tests may be hanging or excessively slow.\nSubsequent runs will use the cached dependencies, and will thus run much more quickly.\n\n### Running Tests from Android Studio\n\nOnce your module and tests have been transpiled, you can run the JUnit tests directly from Android Studio. To open the tests for a module created with `skip init`, navigate to your module's `Skip/build/\u003cmodule\u003e.output/\u003cmodule\u003eTests/skipstone` folder. Control-click the `settings.gradle.kts` file and select 'Open with External Editor' from the resulting context menu.\n\nRunning in Android Studio allows you to bypass the iOS tests and to run - and to debug - individual Android tests. This can be helpful when tracking down Android-specific failures.\n\n### Test Failures\n\nTest failures differ in the XCTest and JUnit worlds. \n\nWhen an `XCTAssert*` failure occurs in Swift, the test failure is noted, but the test continues to run.\nWhen the adapted `assert*` failure occurs in Kotlin, that failure is signalled by an exception, which halts the execution of that test method.\nThis distinction can be noted in the differing number of test failures that occur when mulitple `XCTAssert*` failures occur.\nThe same applies to `XCTFail`, but not to `XCTSkip`, which is the supported and recommended way to prevent tests from running in one or the other environment.\n\n## Implementation Notes\n\nThe adaptation from Swift XCUnit to Kotlin JUnit test cases is quite simple.\nFor example:\n\n```kotlin\nfun XCTAssertEqual(a: Any?, b: Any?): Unit = org.junit.Assert.assertEquals(b, a)\n```\n\n### Transpiled Kotlin Test Case\n\nWhile you may never need to interact with it directly,\nthe transpiled Kotlin for the example test case above looks like this:\n\n```kotlin\npackage app.module.name\n\nimport skip.lib.*\n\nimport skip.unit.*\n\ninternal class MyUnitTests: XCTestCase {\n    @Test\n    internal fun testSomeLogic() = XCTAssertEqual(1 + 2, 3, \"basic test\")\n}\n```\n\n\n## License\n\nThis software is licensed under the \n[Mozilla Public License 2.0](https://www.mozilla.org/MPL/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskiptools%2Fskip-unit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskiptools%2Fskip-unit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskiptools%2Fskip-unit/lists"}