{"id":20721624,"url":"https://github.com/fslev/jtest-utils","last_synced_at":"2025-04-23T15:15:04.723Z","repository":{"id":37844107,"uuid":"304262447","full_name":"fslev/jtest-utils","owner":"fslev","description":"Match Jsons, Xmls, HTTP Responses and simple  texts with regex, polling and data capture support.","archived":false,"fork":false,"pushed_at":"2025-04-14T19:06:38.000Z","size":750,"stargazers_count":5,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-23T15:14:55.235Z","etag":null,"topics":["assertion","capture","diff","httpresponse","java","json","match","polling","regex","testing","xml"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fslev.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,"zenodo":null}},"created_at":"2020-10-15T08:40:02.000Z","updated_at":"2025-04-14T19:06:49.000Z","dependencies_parsed_at":"2023-10-24T14:33:18.860Z","dependency_job_id":"bcc79e5a-0700-421b-9448-91e70222c2ff","html_url":"https://github.com/fslev/jtest-utils","commit_stats":null,"previous_names":[],"tags_count":109,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fslev%2Fjtest-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fslev%2Fjtest-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fslev%2Fjtest-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fslev%2Fjtest-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fslev","download_url":"https://codeload.github.com/fslev/jtest-utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250457792,"owners_count":21433734,"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":["assertion","capture","diff","httpresponse","java","json","match","polling","regex","testing","xml"],"created_at":"2024-11-17T03:28:44.245Z","updated_at":"2025-04-23T15:15:04.717Z","avatar_url":"https://github.com/fslev.png","language":"Java","readme":"# JTest Utils \u003csup\u003e[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)\u003c/sup\u003e\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.fslev/jtest-utils.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.fslev%22%20AND%20a:%22jtest-utils%22)\n![Build status](https://github.com/fslev/jtest-utils/actions/workflows/maven.yml/badge.svg?branch=main)\n[![Coverage Status](https://coveralls.io/repos/github/fslev/jtest-utils/badge.svg?branch=main)](https://coveralls.io/github/fslev/jtest-utils?branch=main)\n\nA set of testing utilities for Java\n\n## Brief\n\n[JTest-Utils](https://github.com/fslev/jtest-utils) contains a set of utility classes which bump up your test framework\nby adding some powerful features:\n\n- **[Matching](#match)**\n- **[Polling](#polling)** (Deprecated)\n- **[Resource reader](#resource-reader)**\n\n**[Real world examples](#real-world)**\n\n#### Maven Central\n\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.fslev\u003c/groupId\u003e\n  \u003cartifactId\u003ejtest-utils\u003c/artifactId\u003e\n  \u003cversion\u003e${latest.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\nGradle: compile(\"io.github.fslev:jtest-utils:${latest.version}\")\n```  \n\n# \u003ca name=\"match\"\u003e\u003c/a\u003e 1. Matching\n\n- Match JSONs\n- Match XMLs\n- Match texts\n- Match Objects\n- Match HTTP responses\n\n_... with specific matching conditions, regular expression data capture and polling support_\n\n## \u003ca name=\"match-jsons\"\u003e\u003c/a\u003e 1.1 Match JSONs\n\nBased on [json-compare](https://github.com/fslev/json-compare)\n\n_Example:_\n\n```javascript\nString expected = \"{\\n\" +\n        \"  \\\"copper\\\": [\\n\" +\n        \"    {\\n\" +\n        \"      \\\"beneath\\\": \\\"heard\\\",\\n\" +\n        \"      \\\"jack\\\": false,\\n\" +\n        \"      \\\"men\\\": -1365455482\\n\" +\n        \"    },\\n\" +\n        \"    \\\"equipment\\\",\\n\" +\n        \"    false\\n\" +\n        \"  ],\\n\" +\n        \"  \\\"speak\\\": -263355062.75097084,\\n\" +\n        \"  \\\"basis\\\": 1670107599\\n\" +\n        \"}\";\nString actual = \"{\\n\" +\n        \"  \\\"copper\\\": [\\n\" +\n        \"    {\\n\" +\n        \"      \\\"beneath\\\": \\\"heard\\\",\\n\" +\n        \"      \\\"men\\\": -1365455482\\n\" +\n        \"    },\\n\" +\n        \"    \\\"equipment\\\",\\n\" +\n        \"    false\\n\" +\n        \"  ],\\n\" +\n        \"  \\\"speak\\\": -263355062.750,\\n\" +\n        \"  \\\"nr1\\\": 62.750,\\n\" +\n        \"  \\\"nr2\\\": 60.750\\n\" +\n        \"}\";\nObjectMatcher.matchJson(\"Seems that JSONs do not match\", expected, actual,\n        MatchCondition.JSON_NON_EXTENSIBLE_OBJECT, MatchCondition.JSON_STRICT_ORDER_ARRAY); // matching fails\n==\u003e\n\norg.opentest4j.AssertionFailedError: FOUND 4 DIFFERENCE(S):\n\n_________________________DIFF__________________________\ncopper -\u003e JSON ARRAY elements differ at position 1:\n{\n  \"beneath\" : \"heard\",\n  \"jack\" : false,\n  \"men\" : -1365455482\n}\n________diffs________\nField 'jack' was NOT FOUND\n\n_________________________DIFF__________________________\nspeak -\u003e \nExpected value: -263355062.75097084 But got: -263355062.750\n\n_________________________DIFF__________________________\nField 'basis' was NOT FOUND\n\n_________________________DIFF__________________________\nActual JSON OBJECT has extra fields\n\nSeems that JSONs do not match\nJSONs do not match\n```\n\n## \u003ca name=\"match-xmls\"\u003e\u003c/a\u003e 1.2 Match XMLs\n\nBased on [xmlunit](https://github.com/xmlunit/xmlunit)\n\n_Example:_\n\n```javascript\nString expected = \"\u003ca id=\\\"1\\\"\u003e \u003clorem\u003eipsum\u003c/lorem\u003e \u003c/a\u003e\";\nString actual = \"\u003ca id=\\\"2\\\"\u003e \u003clorem\u003eipsum\u003c/lorem\u003e \u003c/a\u003e\";\nObjectMatcher.matchXml(\"Seems that XMLs do not match\",\n        expected, actual, MatchCondition.XML_CHILD_NODELIST_LENGTH); // matching fails\n==\u003e \n\njava.lang.AssertionError: Seems that XMLs do not match\nXMLs do not match\n\nMatching is by default done using regular expressions.\nIf expected object contains any unintentional regexes, then quote them between \\Q and \\E delimiters.\n\n\nExpected: Expected attribute name '/a[1]/@id' - comparing \u003ca...\u003e at /a[1]/@id to \u003ca...\u003e at /a[1]:\n\u003ca id=\"1\"\u003e\n  \u003clorem\u003eipsum\u003c/lorem\u003e\n\u003c/a\u003e\n     but: result was: \n\u003ca id=\"2\"\u003e\n  \u003clorem\u003eipsum\u003c/lorem\u003e\n\u003c/a\u003e\n```\n\n## \u003ca name=\"match-texts\"\u003e\u003c/a\u003e 1.3 Match texts\n\nMatch texts with regex support:\n\n```javascript\nString expected = \"lo.*sum \\\\Q(test)\\\\E\";\nString actual = \"lorem \\n ipsum (test)\";\nObjectMatcher.matchString(\"Texts do not match\", expected, actual); // successful matching\nObjectMatcher.matchString(\"Texts do match, actually\", expected, actual, MatchCondition.DO_NOT_MATCH); // matching fails\n\n--\u003e\norg.opentest4j.AssertionFailedError: Texts do match, actually\n\nStrings match!\n```\n\n## 1.4 Match Objects\n\nWhile matching any two Objects using `ObjectMatcher.match()`, one of the matching mechanisms from [above](#match) will be applied in this order:  \n\n\u003e if Objects can be converted to JSON, then match as JSONs\n\u003e\u003e else, if Objects are XML strings, then match as XMLs\n\u003e\u003e\u003e else, match objects as texts\n\n_Example:_\n\n```javascript\nString expected = \"{\\\"a\\\":1}\";\nString actual = \"{\\\"a\\\":1}\";\nObjectMatcher.match(\"Objects were converted and matched as JSONs\", expected, actual); // successful matching\n\nexpected = \"\u003ca\u003e1\u003c/a\u003e\";\nactual = \"\u003ca\u003e1\u003c/a\u003e\";\nObjectMatcher.match(\"Objects were converted and matched as XMLs\", expected, actual); // successful matching\n\nexpected = \"{\\\"a\\\":i am not a json}\";\nactual = \"{\\\"a\\\":i am not a json}\";\nObjectMatcher.match(\"Objects were matched as texts\", expected, actual); // successful matching\n```\n\n## 1.5 Match HTTP responses\nIf your test framework is querying REST services and makes assertions on the response data, then you might find this type of matching very useful.  \n_Example:_\n```javascript\nString expected = \"{\\\"status\\\": 200, \\\"headers\\\":[{\\\"Content-Length\\\":\\\"157\\\"}], \\\"body\\\":{\\\"employee\\\":\\\"John Johnson\\\"}}\";\nString actual = \"{\\\"status\\\": 200, \\\"headers\\\":[{\\\"Content-Length\\\":\\\"157\\\"}], \\\"body\\\":{\\\"employee\\\":\\\"John Johnny\\\"}}\";\nObjectMatcher.matchHttpResponse(\"Matching failure\", from(expected), from(actual)); // matching fails\n\n--\u003e\norg.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):\n\n\n_________________________DIFF__________________________\nemployee -\u003e \nExpected value: \"John Johnson\" But got: \"John Johnny\"\n\nHTTP Response bodies do not match!\nMatching failure\n\nJSONs do not match\n```\nFirst off, this is a show purpose example, where the actual HTTP response is represented as text, converted to `io.jtest.utils.matcher.http.PlainHttpResponse` and passed to the _ObjectMatcher.matchHttpResponse()_ method.  \nNormally, the __actual__ object is a real HTTP response received from server and has many implementations depending on the HTTP client you are using. For this, you have to build the `PlainHttpResponse` object yourself, by using its builder: `PlainHttpResponse.Builder.create()`.  \nThe __expected__ object is usually represented as text and can be simply converted to `io.jtest.utils.matcher.http.PlainHttpResponse` by using any library with object deserialization support:  \n  \n_Example:_\n```javascript\nimport io.jtest.utils.matcher.http.PlainHttpResponse;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic static PlainHttpResponse from(String content) {\n    try {\n        return new ObjectMapper().readValue(content, PlainHttpResponse.class);\n    } catch (JsonProcessingException e) {\n        throw new PlainHttpResponse.ParseException(\"Cannot parse content\", e);\n    }\n}\n```\nThe beautiful part while comparing HTTP responses is the fact that depending on the type of the response body, one of the matching mechanisms from above will be automatically applied.  \nIn other words, the HTTP response bodies / entities might be matched as [JSONs](#match-jsons), [XMLs](#match-xmls) or [texts](#match-texts).  \nHTTP statuses and reasons are compared as [texts](#match-texts) while HTTP headers as [JSONs](#match-jsons).  \n\n## 1.6 Match with Polling support (Deprecated)\nAll the matching mechanisms from above support polling.  \nThe most used case is when we need to compare HTTP responses from a service which delivers the desired data asynchronously.  \nIn this case we need to retry the execution of client request until the actual response is matched.  \n```javascript\nString expected = \"{\\\"status\\\": 200, \\\"body\\\":{\\\"employee\\\":\\\"John Johnson\\\"}}\";\nObjectMatcher.matchHttpResponse(\"Result not found\", from(expected),\n        () -\u003e from(client.execute()),\n        Duration.ofSeconds(30), 1000L);\n```\n  \n  \n\n## 1.7 Match and Capture\nOnly comparing objects is not enough in the real world of testing. Often, we need to parse the results that we've just matched, extract specific data and use it inside the next test step. Writing code for this, repeatedly, can be cumbersome.  \nSo, in case of successful matching, we can extract the data we need by using custom placeholders delimited by  \n`~[` and `]` inside the __expected__ object: \n  \n```javascript\nString expected = \"{\\n\" +\n        \"  \\\"copper\\\": [\\n\" +\n        \"    {\\n\" +\n        \"      \\\"beneath\\\": \\\"~[someValueForBeneath]\\\"\\n\" +\n        \"    }\\n\" +\n        \"  ],\\n\" +\n        \"  \\\"speak\\\": \\\"~[speakValue]\\\"\\n\" +\n        \"}\";\nString actual = \"{\\n\" +\n        \"  \\\"copper\\\": [\\n\" +\n        \"    {\\n\" +\n        \"      \\\"beneath\\\": \\\"heard\\\",\\n\" +\n        \"      \\\"men\\\": -1365455482\\n\" +\n        \"    }\\n\" +\n        \"  ],\\n\" +\n        \"  \\\"speak\\\": -263355062.750,\\n\" +\n        \"  \\\"nr2\\\": 60.750\\n\" +\n        \"}\";\n        \nMap\u003cString, Object\u003e capturedData = ObjectMatcher.matchJson(null, expected, actual);\n\nassertEquals(\"heard\", capturedData.get(\"someValueForBeneath\"));\nassertEquals(\"-263355062.750\", capturedData.get(\"speakValue\"));\n```  \nThis feature is available for any flavour of object matching (String, Json or XML).    \n  \n# \u003ca name=\"polling\"\u003e\u003c/a\u003e 2. Polling (Deprecated)\nRetry an operation until desired result or timeout is reached:  \n```javascript\nInteger result = new Polling\u003cInteger\u003e()\n        .duration(Duration.ofSeconds(30), 5L)\n        .exponentialBackOff(1.0)\n        .supplier(() -\u003e generateRandomFromInterval(4, 7))\n        .until(number -\u003e number == 6).get();\n        \nassertEquals(6, result);\n```\n\n# \u003ca name=\"resource-reader\"\u003e\u003c/a\u003e 3. Resource reader\n\nRead resource as String from either classpath or absolute path:\n```javascript\nResourceUtils.read(\"features/file.txt\") // relative to classpath\nResourceUtils.read(\"/opt/project/hello/src/test/resources/features/file.txt\") // absolute path\n```\nRecursively read all files from a directory, using extension filter: \n```javascript\nMap\u003cString, String\u003e data = ResourceUtils.readDirectory(\"features/data\", \".properties\", \".txt\", \".html\", \".json\");\n\nassertEquals(\"lorem content\", data.get(\"features/data/foo.txt\"));\nassertEquals(\"\u003chtml\u003etext\u003c/html\u003e\", data.get(\"features/data/bar.html\"));\nassertEquals(\"{\\\"elephants\\\" : 1}\", data.get(\"features/data/zoo/animals.json\"));\n```\n\n# \u003ca name=\"real-world\"\u003e\u003c/a\u003e 4. Real world examples\nExplore [cucumber-jutils-tutorial](https://github.com/fslev/cucumber-jutils-tutorial) to discover how most of these features are used in real life test scenarios.\n\n# Website\nhttps://fslev.github.io/jtest-utils/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffslev%2Fjtest-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffslev%2Fjtest-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffslev%2Fjtest-utils/lists"}