{"id":18012878,"url":"https://github.com/tonivade/zeromock","last_synced_at":"2025-08-29T05:08:07.754Z","repository":{"id":47088089,"uuid":"119746908","full_name":"tonivade/zeromock","owner":"tonivade","description":"Mock Http Server with zero dependencies","archived":false,"fork":false,"pushed_at":"2025-08-28T06:07:36.000Z","size":2217,"stargazers_count":20,"open_issues_count":6,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-28T12:51:50.817Z","etag":null,"topics":["dsl","http","http-server","mock","rest"],"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/tonivade.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null}},"created_at":"2018-01-31T21:39:44.000Z","updated_at":"2025-08-28T06:07:33.000Z","dependencies_parsed_at":"2023-02-18T11:17:51.686Z","dependency_job_id":"ba594e10-1175-4390-b7ba-90f6e392391b","html_url":"https://github.com/tonivade/zeromock","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/tonivade/zeromock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonivade%2Fzeromock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonivade%2Fzeromock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonivade%2Fzeromock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonivade%2Fzeromock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonivade","download_url":"https://codeload.github.com/tonivade/zeromock/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonivade%2Fzeromock/sbom","scorecard":{"id":541929,"data":{"date":"2025-08-11","repo":{"name":"github.com/tonivade/zeromock","commit":"26fac1829df23b06de66a048d8938a455a5ae237"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/3 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/gradle.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/tonivade/zeromock/gradle.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/tonivade/zeromock/gradle.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/tonivade/zeromock/gradle.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/tonivade/zeromock/gradle.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T08:23:59.014Z","repository_id":47088089,"created_at":"2025-08-20T08:23:59.014Z","updated_at":"2025-08-20T08:23:59.014Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272541757,"owners_count":24952463,"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","status":"online","status_checked_at":"2025-08-28T02:00:10.768Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dsl","http","http-server","mock","rest"],"created_at":"2024-10-30T03:19:22.569Z","updated_at":"2025-08-29T05:08:07.740Z","avatar_url":"https://github.com/tonivade.png","language":"Java","readme":"# ZeroMock\n\nHttp Mock Server with (mostly) zero dependencies.\n\nRight now the unique dependencies are minimal-json parser and jaxb, used for object serialization. But this project doesn't depends on any java servlet container or something like that. It uses the [simple http server](https://openjdk.org/jeps/408) implementation that comes on every jdk.\n\n![Build Status](https://github.com/tonivade/zeromock/workflows/Java%20CI%20with%20Gradle/badge.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a3718bd59d674b8592065ac84abdf82c)](https://www.codacy.com/app/tonivade/zeromock?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=tonivade/zeromock\u0026amp;utm_campaign=Badge_Grade)\n[![Sonarcloud Badge](https://sonarcloud.io/api/project_badges/measure?project=com.github.tonivade%3Azeromock-parent\u0026amp;metric=alert_status)](https://sonarcloud.io/dashboard?id=com.github.tonivade%3Azeromock-parent)\n[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/a3718bd59d674b8592065ac84abdf82c)](https://www.codacy.com/app/tonivade/zeromock?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=tonivade/zeromock\u0026utm_campaign=Badge_Coverage)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.tonivade/zeromock-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.tonivade/zeromock-api) [![Join the chat at https://gitter.im/tonivade/zeromock](https://badges.gitter.im/tonivade/zeromock.svg)](https://gitter.im/tonivade/zeromock?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n## Why?\n\nUsually, the sorter answer should be 'because I can', but I want to explain a bit why I developed this library, and the principal reason is frustration.\n\n  - Frustration 1: Existing alternatives (like Wiremock, and others) are, in my opinion, too complex and dificult to read. In my daily work if I have to read an existing test that needs many stubs to work, is too difficult to understand the purpose of the test.\n  \n  - Frustration 2: Why I need different libraries to implement a REST API and stubs for this API, in the end, you are doing the same, but instead to compute a result, you are returning a mock result. But finally, both are the same thing.\n  \n  - Frustration 3: I have another project, called [resp-server](https://github.com/tonivade/resp-server), is an implementation of REdis Serialization Protocol, and I thought that it would be nice to use this protocol to develop REST API applications. I tried to call spring-mvc controller using RESP protocol but the result was an ugly hack. I tried the same using spring-webflux, but the result was the same. So, why I cannot decouple HTTP protocol from the controller implementation?\n\nSo, these are the objetives of this library:\n  - provide an independent/decoupled/reusable implementation of REST API controllers.\n  - provide a fluent API to make easy to read and maintain applications/tests.\n  - keep the library small with the minimum of dependencies.\n\n## How?\n\nFirst of all, I implemented the model, `HttpRequest` and `HttpResponse`, immutable objects, and then I implemented the functions to control the model transformations to obtain a response from a request. In the end the controller is a simple `Function\u003cHttpRequest, HttpResponse\u003e`.\n\nThen, I created the server based in the `HttpServer` included inside the JRE. This server is under a `com.sun` package, so, it's not recomended to use, but for my test purposes it's more than enough.\n\nAlso I implemented a simple `HttpClient` using `HttpURLConnection`, initially only for test purposes.\n\nAnd finally, I implemented a Junit5 extension for Junit integration.\n\n## Examples\n\nThis is the simplest example, a ping application. It uses the Junit5 extension. The test receives an instance of the server. Then you can create the stubs you need.\n\n```java\n  @Test\n  public void ping(MockHttpServer server) {\n    server.when(Matchers.get(\"/ping\")).then(Handlers.ok(\"pong\"));\n    \n    HttpResponse response = HttpClient.connectTo(BASE_URL).request(Requests.get(\"/ping\"));\n    \n    assertEquals(\"pong\", Bytes.asString(response.body()));\n  }\n```\n\n`MockHttpServer` is the principal class, it listen for requests, finds for a handler, and then executes it. If no handler is found, a `NOT_FOUND(404)` is returned.\n\nThe class `Matchers` contains predicates of type `Predicate\u003cHttpRequest\u003e`, with these predicates you can create the matcher function. These functions can be composes using the existing `Predicate` combinators like `and`, `or` and `negate`. In this case, it defines a predicate that matches a `GET` command with `/ping` path.\n\nThe class `Handlers` contains functions of type `Function\u003cHttpRequest, HttpResponse\u003e` to represent the response. Also you can combine different functions using `Function` combinators like `andThen` or `compose`. In this case, it defines a `OK(200)` response with `pong` content.\n\nThe class `Bytes` is the wrapper class I use for request and response bodies. It contains a `ByteBuffer` with the raw data. Also this class defines utillity methods to create and convert `Bytes`. In this case, it converts the response body to a `String`.\n\nAnother example of echo:\n\n```java\n  @Test\n  public void echoQueryParam(MockHttpServer server) {\n    server.when(Matchers.get(\"/echo\").and(Matchers.param(\"say\")))\n          .then(Handlers.ok(Extractors.queryParam(\"say\").andThen(Serializers.plain())));\n    \n    HttpResponse response = HttpClient.connectTo(BASE_URL)\n        .request(Requests.get(\"/echo\").withParam(\"say\", \"Hello World!\"));\n    \n    assertEquals(\"Hello World!\", Bytes.asString(response.body()));\n  }\n```\n\nThe class `Extractors` contains functions of type `Function\u003cHttpRequest, T\u003e` to extract data from an `HttpRequest`. In this case extracts the content of the query param `say`.\n\nThe class `Serializers` contains functions of type `Function\u003cT, Bytes\u003e` to convert results to `Bytes`. In this case, it uses a plain text serializer.\n\nThe same example using path params:\n\n```java\n  @Test\n  public void echoPathParam(MockHttpServer server) {\n    server.when(Matchers.get(\"/echo/:message\")) \n          .then(Handlers.ok(Extractors.pathParam(1).andThen(Serializers.plain())));\n    \n    HttpResponse response = HttpClient.connectTo(BASE_URL).request(Requests.get(\"/echo/saysomething\"));\n    \n    assertEquals(\"saysomething\", Bytes.asString(response.body()));\n  }\n```\n\nTo declare a path param, you have to use the colon prefix `:` before the name of the param. To access the value you have to use the position of the parameter in the path.\n\nAnd the final example, by now, is an implementation of the echo server using json serialization\n\n```java\n  @Test\n  public void pojoSerialization(MockHttpServer server) {\n    server.when(Matchers.get(\"/echo\").and(Matchers.param(\"say\"))) \n          .then(Handlers.ok(Extractors.queryParam(\"say\").andThen(Say::new).andThen(Serializers.json())));\n    \n    HttpResponse response = HttpClient.connectTo(BASE_URL)\n        .request(Requests.get(\"/echo\").withParam(\"say\", \"Hello World!\"));\n    \n    assertEquals(new Say(\"Hello World!\"), asObject(response.body()));\n  }\n  \n  private Say asObject(Bytes body) {\n    return Deserializers.json(Say.class).apply(body);\n  }\n```\n\nIn this example, a class `Say` is created with the `say` param content, and finally it converts the pojo using json.\n\nIn the other side, the client can use `Deserializers` class in order to create a Say class again. This class contains functions of type `Function\u003cBytes, T\u003e` that converts `Bytes` to arbitrary objects.\n\nOf course, you can use static imports for a clearer code if you want, I have added the full names only for explanatory purposes. All examples taken from this [test class](https://github.com/tonivade/zeromock/blob/master/test/junit5/src/test/java/com/github/tonivade/zeromock/junit5/ExamplesTest.java)\n\n## One Line Server\n\nIt's pretty simple, using jbang\n\n```java\n//usr/bin/env jbang \"$0\" \"$@\" ; exit $?\n\n//DEPS com.github.tonivade:zeromock-server:1.2\n//DEPS ch.qos.logback:logback-classic:1.5.18\n\nimport static com.github.tonivade.zeromock.server.MockHttpServer.listenAt;\n\npublic class OneLineApplication {\n\n  public static void main(String[] args) {\n    listenAt(8080)\n        .get(\"/ping\").ok(\"pong\")\n        .start();\n  }\n}\n```\n\nand with scala-cli:\n\n```scala\n#!/usr/bin/env -S scala-cli shebang\n\n//\u003e using dep com.github.tonivade:zeromock-server:1.2\n//\u003e using dep ch.qos.logback:logback-classic:1.5.18\n\nimport com.github.tonivade.zeromock.server.MockHttpServer.listenAt\n\n@main def server() =\n  listenAt(8080)\n    .get(\"/ping\").ok(\"pong\")\n    .start()\n```\n\n## License\n\nThis project is released under MIT License\n","funding_links":[],"categories":["测试"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonivade%2Fzeromock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonivade%2Fzeromock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonivade%2Fzeromock/lists"}