|
| 1 | +# keploy-junit5 |
| 2 | + |
| 3 | +A JUnit 5 (Jupiter) extension that joins a test suite to keploy's Universal |
| 4 | +Mocker. `keploy mock record` keeps the calls each test's code makes to its |
| 5 | +dependencies; `keploy mock replay` answers them from the recording, so the |
| 6 | +suite runs with every dependency stopped. keploy judges each test: a call with |
| 7 | +no recorded answer, or a recorded call the test no longer makes, fails that |
| 8 | +test with keploy's text. |
| 9 | + |
| 10 | +The extension only tells keploy where each test begins and ends; keploy keeps |
| 11 | +every rule. It speaks the same wire as go-sdk's `keploytest`, |
| 12 | +`@keploy/playwright`, `@keploy/jest` and `keploy-pytest`. |
| 13 | + |
| 14 | +## Use |
| 15 | + |
| 16 | +JUnit 5.10 or later (JUnit 6 included), Java 8 or later. The package is not |
| 17 | +published yet: install it from this folder with `mvn install`, then add it to |
| 18 | +the test scope: |
| 19 | + |
| 20 | +```xml |
| 21 | +<dependency> |
| 22 | + <groupId>io.keploy</groupId> |
| 23 | + <artifactId>keploy-junit5</artifactId> |
| 24 | + <version>0.1.0-SNAPSHOT</version> |
| 25 | + <scope>test</scope> |
| 26 | +</dependency> |
| 27 | +``` |
| 28 | + |
| 29 | +Let JUnit load it for the whole run, in `src/test/resources/junit-platform.properties`: |
| 30 | + |
| 31 | +```properties |
| 32 | +junit.jupiter.extensions.autodetection.enabled=true |
| 33 | +``` |
| 34 | + |
| 35 | +(Gradle: `test { useJUnitPlatform(); systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' }`.) |
| 36 | + |
| 37 | +keploy needs the project's `keploy.yml` to name the recording and CI's test |
| 38 | +command; without `mock.command`, keploy refuses the run with |
| 39 | +`no keploy project here: set mock.command ...`: |
| 40 | + |
| 41 | +```yaml |
| 42 | +# keploy.yml |
| 43 | +mock: |
| 44 | + name: shop # the recording: keploy/shop/ |
| 45 | + command: mvn test # CI's test command |
| 46 | +``` |
| 47 | +
|
| 48 | +Then run the suite under keploy with `KEPLOY_PROVEN=1`, in one test JVM. |
| 49 | +`--local` keeps the recording on disk, in `keploy/<name>/`: |
| 50 | + |
| 51 | +```sh |
| 52 | +KEPLOY_PROVEN=1 keploy mock record --local -c "mvn test" |
| 53 | +KEPLOY_PROVEN=1 keploy mock replay --local -c "mvn test" |
| 54 | +``` |
| 55 | + |
| 56 | +Without keploy (`KEPLOY_MOCK_AGENT` unset or `off`) the extension does |
| 57 | +nothing, and the suite runs as plain JUnit. |
| 58 | + |
| 59 | +## What it does under keploy |
| 60 | + |
| 61 | +- The test JVM claims keploy's run once, before the first test class's |
| 62 | + `@BeforeAll`, naming keploy's project folder (`KEPLOY_MOCK_PROJECT`). |
| 63 | +- Each test begins after its class's `@BeforeAll` methods, before every other |
| 64 | + extension's BeforeEach callback and its own `@BeforeEach` methods. It ends |
| 65 | + after its `@AfterEach` methods and every other extension's AfterEach |
| 66 | + callback, with its outcome: passed, failed, or skipped (a failed |
| 67 | + assumption). The calls `@BeforeAll` and `@AfterAll` methods make belong to |
| 68 | + the test JVM, outside every test. |
| 69 | +- A test keploy set aside is aborted (JUnit's skip) with keploy's reason, and |
| 70 | + none of its `@BeforeEach`, test or `@AfterEach` methods runs (other |
| 71 | + extensions' AfterEach callbacks do, as JUnit runs them after any BeforeEach |
| 72 | + callback stops a test). A line on stderr names it. |
| 73 | +- A test keploy failed at its end fails with keploy's text; when it had failed |
| 74 | + already, keploy's text is added to its failure. |
| 75 | +- Its ID is `<class>::<method>`: the class's binary name |
| 76 | + (`com.shop.OrdersTest$Refunds` for a `@Nested` class) and JUnit's name of |
| 77 | + the method, with a parameterized or repeated test's invocation index: |
| 78 | + `com.shop.OrdersTest::price(java.lang.String)[2]`. Each invocation of a |
| 79 | + `@ParameterizedClass` (JUnit 5.13 and later) runs each of the class's tests |
| 80 | + as a test of its own, with the class's invocation index before the method: |
| 81 | + `com.shop.OrdersTest::[2]price(java.lang.String)`. A test run through a |
| 82 | + JUnit Platform `@Suite` has the same ID as when it runs directly. |
| 83 | + |
| 84 | +## Limits |
| 85 | + |
| 86 | +Each of these fails loudly under keploy, so no Jupiter test runs unjudged: |
| 87 | + |
| 88 | +- One test JVM at a time: keploy refuses a second claim. Keep Surefire's |
| 89 | + `forkCount` at 1 (its default), and Gradle's `maxParallelForks` at 1. |
| 90 | +- JUnit's parallel execution (`junit.jupiter.execution.parallel.enabled=true`) |
| 91 | + fails every test class under keploy. |
| 92 | +- Registering the extension only with `@ExtendWith`, without autodetection, |
| 93 | + fails every test class under keploy: some classes could run without it, |
| 94 | + and it must wrap every other extension. |
| 95 | +- A `@TestFactory`'s dynamic tests run inside one BeforeEach/AfterEach pair, |
| 96 | + so keploy cannot judge them one by one: the factory fails under keploy. |
| 97 | + Write them as `@ParameterizedTest`. |
| 98 | +- A test begun a second time in one JVM fails (a rerun, such as Surefire's |
| 99 | + `rerunFailingTestsCount`, or a second `@Suite` that selects the same test |
| 100 | + class): keploy names a test by its class and name. |
| 101 | +- A JVM cannot change its own environment, so it does not export |
| 102 | + `KEPLOY_TEST_PID` to the processes its tests start; one of them that runs |
| 103 | + tests under a keploy SDK claims too, and keploy refuses it. |
| 104 | + |
| 105 | +Only JUnit Jupiter tests are judged. The tests of every other JUnit Platform |
| 106 | +engine run unjudged, as without keploy: JUnit 4 tests on the Vintage engine, |
| 107 | +and Spock, Kotest, jqwik and Cucumber tests. This extension is a Jupiter |
| 108 | +extension, and no other engine calls it; their calls are the test JVM's. |
| 109 | + |
| 110 | +## Releasing |
| 111 | + |
| 112 | +keploy-junit5 is released on its own tag, with its own version, apart from |
| 113 | +the repository's `v*.*.*` releases of the agent. To release X.Y.Z, push the |
| 114 | +tag `keploy-junit5-vX.Y.Z` on a commit of `main`: |
| 115 | +`git tag keploy-junit5-vX.Y.Z <sha> && git push origin keploy-junit5-vX.Y.Z`. |
| 116 | +The tag runs the rows and the e2e lane, then |
| 117 | +`.github/workflows/keploy-junit5-release.yml` sets the pom's version to X.Y.Z |
| 118 | +for that run only, builds the jar with its sources and javadoc jars, signs |
| 119 | +each, and deploys them to Maven Central. It uses the repository secrets that |
| 120 | +`release.yml` uses: `CENTRAL_USERNAME` and `CENTRAL_PASSWORD` (a Central |
| 121 | +Portal user token for the `io.keploy` namespace), `MAVEN_GPG_PRIVATE_KEY` and |
| 122 | +`MAVEN_GPG_PASSPHRASE`; it refuses to start, naming each one that is not set. |
0 commit comments