{"id":23640574,"url":"https://github.com/tginsberg/junit5-system-exit","last_synced_at":"2025-07-31T23:34:06.182Z","repository":{"id":44150634,"uuid":"157116589","full_name":"tginsberg/junit5-system-exit","owner":"tginsberg","description":"A JUnit5 Extension to help write tests that call System.exit()","archived":false,"fork":false,"pushed_at":"2024-10-26T22:29:57.000Z","size":98,"stargazers_count":51,"open_issues_count":3,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-02T03:27:07.451Z","etag":null,"topics":["java","junit","junit5"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tginsberg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2018-11-11T20:08:13.000Z","updated_at":"2025-03-28T09:45:00.000Z","dependencies_parsed_at":"2024-12-28T09:27:10.930Z","dependency_job_id":"e85ba54a-eb9e-457f-b30f-aada85206c26","html_url":"https://github.com/tginsberg/junit5-system-exit","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tginsberg%2Fjunit5-system-exit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tginsberg%2Fjunit5-system-exit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tginsberg%2Fjunit5-system-exit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tginsberg%2Fjunit5-system-exit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tginsberg","download_url":"https://codeload.github.com/tginsberg/junit5-system-exit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247994122,"owners_count":21030050,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["java","junit","junit5"],"created_at":"2024-12-28T09:27:01.761Z","updated_at":"2025-04-09T07:09:40.683Z","avatar_url":"https://github.com/tginsberg.png","language":"Java","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"# JUnit5 System.exit() Extension\n\nThis [JUnit 5 Extension](https://junit.org/junit5/docs/current/user-guide/#extensions) helps you write tests for code that calls `System.exit()`. It requires Java 17 or greater,\nand has one dependency ([ASM](https://asm.ow2.io/)).\n\n## Differences Between Version 1.x and 2.x\n\n**Version 1.x** used an approach that [replaced the system `SecurityManager`](https://todd.ginsberg.com/post/testing-system-exit/).\nThat worked fine until Java 17 where the `SecurityManager` was deprecated for removal. As of Java 18, a property to \nexplicitly enable programmatic access to the `SecurityManager` is required. This method works for now but will eventually \nstop working. If you are still on 1.x and cannot move to 2.x, [you can still find the instructions here](Version1.xInstructions.md).\n\n**Version 2.x** uses a Java Agent to rewrite bytecode as the JVM loads classes. Whenever a call to `System.exit()` is detected, \nthe Junit 5 System Exit Agent replaces that call with a function that records an attempt to exit, preventing the JVM from exiting. \nAs a consequence of rewriting bytecode, this library now has one dependency - [ASM](https://asm.ow2.io/). \nWhen the [Java Class-File API](https://openjdk.org/jeps/457) is released, I will explore using that instead (or in addition to).\n\nVersion 2 also supports AssertJ-style fluid assertions in addition to the annotation-driven approach that came with Version 1. \nOther than enabling the Java Agent (see below), your code should not change when upgrading from Version 1.x to Version 2.x.\n\n## Installing\n\nInstalling involves two steps: adding the `junit5-system-exit` library to your build, and adding its Java Agent to \nyour test task. Please consult the [FAQ](#faq) below if you run into problems.\n\n### Gradle\n\n#### 1. Copy the following into your `build.gradle` or `build.gradle.kts`.\n\n```groovy\ntestImplementation(\"com.ginsberg:junit5-system-exit:2.0.2\")\n```\n\n#### 2. Enable the Java Agent\n\nIt is important to add the Junit5 System Exit Java Agent in a way that makes it come after other agents which\nyou may be using, such as JaCoCo. See the notes below for details on why.\n\nIf you use the Groovy DSL, add this code to your `build.gradle` file in the `test` task...\n\n```groovy\n// Groovy DSL\n\ntest {\n    useJUnitPlatform()\n\n    def junit5SystemExit = configurations.testRuntimeClasspath.files\n            .find { it.name.contains('junit5-system-exit') }\n    jvmArgumentProviders.add({[\"-javaagent:$junit5SystemExit\"]} as CommandLineArgumentProvider)\n}\n```\n\nor if you use the Kotlin DSL...\n\n```kotlin\n// Kotlin DSL\ntest {\n    useJUnitPlatform()\n\n    jvmArgumentProviders.add(CommandLineArgumentProvider {\n        listOf(\"-javaagent:${configurations.testRuntimeClasspath.get().files.find { \n            it.name.contains(\"junit5-system-exit\") }\n        }\")\n    })\n}\n```\n\n### Maven\n\n#### 1. Copy the following into your `pom.xml`\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.ginsberg\u003c/groupId\u003e\n    \u003cartifactId\u003ejunit5-system-exit\u003c/artifactId\u003e\n    \u003cversion\u003e2.0.2\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n#### 2. Enable the Java Agent\n\nIn `pom.xml`, add the `properties` goal to `maven-dependency-plugin`, in order to create properties for each dependency.\n\n```xml\n\u003cplugin\u003e\n    \u003cartifactId\u003emaven-dependency-plugin\u003c/artifactId\u003e\n    \u003cexecutions\u003e\n        \u003cexecution\u003e\n            \u003cgoals\u003e\n                \u003cgoal\u003eproperties\u003c/goal\u003e\n            \u003c/goals\u003e\n        \u003c/execution\u003e\n    \u003c/executions\u003e\n\u003c/plugin\u003e\n```\n\nThen add the following `\u003cargLine/\u003e` to `maven-surefire-plugin`, which references the property we just created for \nthis library. This should account for other Java Agents and run after any others you may have, such as JaCoCo.\n\n```xml\n\u003cplugin\u003e\n    \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n    \u003cartifactId\u003emaven-surefire-plugin\u003c/artifactId\u003e\n    \u003cconfiguration\u003e\n        \u003cargLine\u003e@{argLine} -javaagent:${com.ginsberg:junit5-system-exit:jar}\u003c/argLine\u003e\n    \u003c/configuration\u003e\n\u003c/plugin\u003e\n```\n\nAnd \n\n## Use Cases - Annotation-based\n\n**A Test that expects `System.exit()` to be called, with any status code:**\n\n```java\npublic class MyTestCases { \n    \n    @Test\n    @ExpectSystemExit\n    void thatSystemExitIsCalled() {\n        System.exit(1);\n    }\n}\n```\n\n**A Test that expects `System.exit(1)` to be called, with a specific status code:**\n\n```java\npublic class MyTestCases {\n    \n    @Test\n    @ExpectSystemExitWithStatus(1)\n    void thatSystemExitIsCalled() {\n        System.exit(1);\n    }\n}\n```\n\n**A Test that should not expect `System.exit()` to be called, and fails the test if it does:**\n\n```java\npublic class MyTestCases {\n    \n    @Test\n    @FailOnSystemExit\n    void thisTestWillFail() {\n        System.exit(1); // !!!\n    }\n}\n```\n\nThe `@ExpectSystemExit`, `@ExpectSystemExitWithStatus`, and `@FailOnSystemExit` annotations can be applied to methods, classes, or annotations (to act as meta-annotations).\n\n## Use Cases - Assertion-based\n\n**A Test that expects `System.exit()` to be called, with any status code:**\n\n```java\npublic class MyTestClasses {\n    \n    @Test\n    void thatSystemExitIsCalled() {\n        assertThatCallsSystemExit(() -\u003e \n                System.exit(42)\n        );\n    }\n}\n```\n\n**A Test that expects `System.exit(1)` to be called, with a specific status code:**\n\n```java\npublic class MyTestClasses {\n    \n    @Test\n    void thatSystemExitIsCalled() {\n        assertThatCallsSystemExit(() -\u003e \n                System.exit(42)\n        ).withExitCode(42);\n    }\n}\n```\n\n**A Test that expects `System.exit(1)` to be called, with status code in a specified range (inclusive):**\n\n```java\npublic class MyTestClasses {\n    \n    @Test\n    void thatSystemExitIsCalled() {\n        assertThatCallsSystemExit(() -\u003e \n                System.exit(42)\n        ).withExitCodeInRange(1, 100);\n    }\n}\n```\n\n**A Test that should not expect `System.exit()` to be called, and fails the assertion if it does:**\n\n```java\npublic class MyTestClasses {\n    \n    @Test\n    void thisTestWillFail() {\n        assertThatDoesNotCallSystemExit(() -\u003e\n                System.exit(42) // !!!\n        );\n    }\n}\n```\n\n## FAQ\n\n### :question: I don't want `Junit5-System-Exit` to rewrite the bytecode of a specific class or method that calls `System.exit()`.\n\nThis is supported. When this library detects any annotation called `@DoNotRewriteExitCalls` on any method or class, bytecode \nrewriting will be skipped. While this library ships with its own implementation of `@DoNotRewriteExitCalls`, you'll probably\nwant to write your own so you don't have to use this library outside the test scope. It is a marker annotation like this:\n\n```java\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE, ElementType.METHOD})\npublic @interface DoNotRewriteExitCalls {\n}\n```\n\n### :question: JaCoCo issues a warning - \"Execution data for class \u003csome class\u003e does not match\"\n\nThis happens when JaCoCo's Java Agent runs after this one. The instructions above _should_ put this agent after JaCoCo\nbut things change over time, and there are probably more gradle configurations that I have not tested. If you run into\nthis and are confident that you followed the instructions above, please reach out to me with a minimal example and I will\nsee what I can do.\n\n### :question: JaCoCo coverage is not accurate\n\nBecause this Java Agent rewrites bytecode and must run after JaCoCo, there will be discrepancies in the JaCoCo Report.\nI have not found a way to account for this, but if you discover something please let me know!\n\n## Contributing and Issues\n\nPlease feel free to file issues for change requests or bugs. If you would like to contribute new functionality, please contact me first!\n\nCopyright \u0026copy; 2021-2024 by Todd Ginsberg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftginsberg%2Fjunit5-system-exit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftginsberg%2Fjunit5-system-exit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftginsberg%2Fjunit5-system-exit/lists"}