{"id":15466140,"url":"https://github.com/mbizhani/artemis","last_synced_at":"2025-04-22T11:20:40.324Z","repository":{"id":37040423,"uuid":"387255353","full_name":"mbizhani/Artemis","owner":"mbizhani","description":"A developer-friendly integration test tool for calling REST APIs","archived":false,"fork":false,"pushed_at":"2023-07-31T16:42:51.000Z","size":329,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-22T11:20:05.619Z","etag":null,"topics":["groovy","integration-testing","java","junit","maven-plugin","rest-api","testing"],"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/mbizhani.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}},"created_at":"2021-07-18T19:36:48.000Z","updated_at":"2024-02-13T08:07:26.000Z","dependencies_parsed_at":"2025-03-03T13:38:18.654Z","dependency_job_id":"c6b47b99-fe59-42a4-8ea4-c591f164c11c","html_url":"https://github.com/mbizhani/Artemis","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbizhani%2FArtemis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbizhani%2FArtemis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbizhani%2FArtemis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbizhani%2FArtemis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mbizhani","download_url":"https://codeload.github.com/mbizhani/Artemis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250228696,"owners_count":21395958,"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":["groovy","integration-testing","java","junit","maven-plugin","rest-api","testing"],"created_at":"2024-10-02T01:05:13.517Z","updated_at":"2025-04-22T11:20:40.300Z","avatar_url":"https://github.com/mbizhani.png","language":"Java","readme":"# Artemis\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.devocative.artemis/artemis/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.devocative.artemis/artemis)\n\n![Artemis Structure](/doc/logo.png)\n\n- [Introduction](#introduction)\n- [An Example](#an-example)\n- [Execution](#execution)\n  - [JUnit Integration](#junit-integration)\n  - [Maven Integration](#maven-integration)\n  - [Output](#output)\n- [Description of the Files](#description-of-the-files)\n- [Parallel Execution and Performance Test](#parallel-execution-and-performance-test)\n- [Start Developing](#start-developing)\n  - [`Artemis` Utility/Helper Class](#artemis-utilityhelper-class)\n\n## Introduction\n\nArtemis is an easy and simple `REST API`, `Integration`, and `Performance` test library. You can use your most powerful\ntool to apply Artemis, your IDE. You just need to develop an XML file, and a Groovy file to test your REST APIs. The XML\nfile describes the structure flow of your scenarios by defining all HTTP requests and then asserting the status of\nresponse and its body. The Groovy file contains lots of functions, called by XML file, to present all the behavioral\nparts during your test, such as creating complex dynamic data, asserting the response body with all `Assertions`\nlibraries, and processing the data necessary to the test scenarios. From version 1.3, a cookie store is enabled for\nArtemis, and you can assert cookie presence in `\u003cassertRs cookies=''/\u003e`.\n\nYou can integrate Artemis in two ways into your project:\n\n1. Call its API in a JUnit method to integrate it with your other tests\n2. Add its maven plugin to your pom and then execute its goal in a more manual way\n\n## An Example\n\n**Note:** You can see the complete example at [OnFood](https://github.com/mbizhani/OnFood).\n\nThe following is an `artemis.xml` file:\n\n```xml\n\u003c!DOCTYPE artemis PUBLIC \"-//Devocative.Org//Artemis 1.0//EN\"\n    \"https://devocative.org/dtd/artemis-1.0.dtd\"\u003e\n\n\u003cartemis\u003e\n  \u003cvars\u003e\n    \u003cvar name=\"cell\" value=\"09${_.generate(9, '0'..'9')}\"/\u003e\n    \u003cvar name=\"firstName\" value=\"${_.generate(4, 'a'..'z')}\"/\u003e\n    \u003cvar name=\"lastName\" value=\"${_.generate(4, 'a'..'z')}\"/\u003e\n    \u003c!-- \"password\" is generated in before() --\u003e\n  \u003c/vars\u003e\n\n  \u003cscenario name=\"RegisterRestaurateur\"\u003e\n    \u003cget url=\"/restaurateurs/registrations/${cell}\"\u003e\n\n      \u003c!-- `body` can be `empty`, `text` or `json` (default value) --\u003e\n      \u003cassertRs status=\"200\" body=\"empty\"/\u003e\n    \u003c/get\u003e\n\n    \u003cget url=\"/j4d/registrations/${cell}\"\u003e\n      \u003cassertRs status=\"200\" properties=\"code\"/\u003e\n    \u003c/get\u003e\n\n    \u003cpost url=\"/restaurateurs/registrations\" id=\"register\"\u003e\n\n      \u003c!-- `_prev.rs` refers to the response body of previous request --\u003e\n      \u003cbody\u003e\u003c![CDATA[\n{\n    \"firstName\": \"${firstName}\",\n    \"lastName\": \"${lastName}\",\n    \"cell\": \"${cell}\",\n    \"code\": \"${_prev.rs.code}\",\n    \"password\": \"${password}\"\n}\n            ]]\u003e\u003c/body\u003e\n\n      \u003c!-- \n        Due to `call=\"true\"`, `assertRs_register(Context, Map)` is called in Groovy file.\n        These assert functions must have syntax of `assertRs_ID(Context, Map | List)`, and `ID`\n        is the value of XML's `id` attribute.  \n      --\u003e\n      \u003cassertRs status=\"200\" properties=\"userId,token\" call=\"true\"/\u003e\n    \u003c/post\u003e\n\n    \u003cget url=\"/restaurateurs/${register.rs.userId}\"\u003e\n      \u003cheaders\u003e\n        \u003cheader name=\"Authorization\" value=\"${register.rs.token}\"/\u003e\n      \u003c/headers\u003e\n\n      \u003cassertRs status=\"200\" properties=\"id,firstName,lastName,cell,createdBy,createdDate,version\"/\u003e\n    \u003c/get\u003e\n  \u003c/scenario\u003e\n\u003c/artemis\u003e\n```\n\nand here is the `artemis.groovy` file:\n\n```groovy\nimport org.devocative.artemis.Context\nimport org.junit.jupiter.api.Assertions\n\n// Called at the beginning, `before` sending any request\ndef before(Context ctx) {\n  def password = generate(5, 'a'..'z')\n  def encPass = Base64.getEncoder().withoutPadding().encodeToString(password.getBytes())\n  ctx.addVar(\"password\", encPass)\n\n  // `Artemis` is a utility class with helper functions such as log  \n  Artemis.log(\"Password: main=${password} enc=${encPass}\")\n}\n\ndef generate(int n, List\u003cString\u003e... alphaSet) {\n  def list = alphaSet.flatten()\n  new Random().with {\n    (1..n).collect { list[nextInt(list.size())] }.join()\n  }\n}\n\ndef assertRs_register(Context ctx, Map rsBody) {\n  // `ctx.vars` refers to all defined variables until now  \n  Assertions.assertNotNull(ctx.vars.cell)\n  Assertions.assertNotNull(ctx.vars.firstName)\n  Assertions.assertNotNull(ctx.vars.lastName)\n\n  // By default, `org.junit.jupiter:junit-jupiter-api` is added to Artemis dependency, to \n  // call all `Assertions` methods. You can use any assertions library here.   \n  Assertions.assertNotNull(rsBody.userId)\n  Assertions.assertNotNull(rsBody.token)\n}\n```\n\nBy default, these two files should be in your `src/test/resources` directory of your project or project's module.\n\n## Execution\n\nSome of Artemis parameters are passed before its execution. The most important one is `baseUrl`, which is prepended to\nthe `url` of each request in the XML file. These parameters are passed to `Config` object (`name` is just passed as\nconstructor parameter) or inside `\u003cconfiguration\u003e` in the maven.\n\n| Parameter    | Default Value           | Description                                                                              |\n|--------------|-------------------------|------------------------------------------------------------------------------------------|\n| `name`       | `artemis`               | looking for `\u003cname\u003e.xml` and `\u003cname\u003e.groovy` files for execution                         |\n| `xmlName`    |                         | in case of different name for XML and groovy files                                       |\n| `groovyName` |                         | in case of different name for XML and groovy files                                       |\n| `baseUrl`    | `http://localhost:8080` | prepend it to the `url` of each request in the XML file                                  |\n| `devMode`    | `false`                 | store a memory object as the state of the test for incremental development of test files |\n| `baseDir`    | `src/test/resources`    | looking for the XML and Groovy files in this directory                                   |\n| `parallel`   | `1`                     | number of **parallel** executions of the entire XML file in a thread                     |\n| `loop`       | `1`                     | number of **sequential** executions of the entire XML file in a thread                   |\n| `vars`       |                         | pass variables for scenarios from outside                                                |\n| `proxy`      |                         | pass requests through proxy server, format `socks://HOST:PORT` or `http://HOST:PORT`     |\n\n### JUnit Integration\n\nFirst add the following dependency\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eorg.devocative.artemis\u003c/groupId\u003e\n  \u003cartifactId\u003eartemis-core\u003c/artifactId\u003e\n  \u003cversion\u003e1.5\u003c/version\u003e\n  \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nand then call the Artemis API such as the following example in a Spring Boot application:\n\n```java\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\nclass OnFoodApplicationTests {\n\n  @LocalServerPort\n  private int port;\n\n  @Test\n  void contextLoads() {\n    ArtemisExecutor.run(new Config()\n      .setParallel(5)\n      .setBaseUrl(String.format(\"http://localhost:%s/api\", port))\n    );\n  }\n}\n```\n\n### Maven Integration\n\nAdd the following plugin to your `pom.xml` file. Then you can execute the `artemis:run` goal.\n\n```xml\n\u003cplugin\u003e\n  \u003cgroupId\u003eorg.devocative.artemis\u003c/groupId\u003e\n  \u003cartifactId\u003eartemis-maven-plugin\u003c/artifactId\u003e\n  \u003cversion\u003e1.5\u003c/version\u003e\n  \u003cconfiguration\u003e\n    \u003cbaseUrl\u003ehttp://localhost:8080/api\u003c/baseUrl\u003e\n    \u003cdevMode\u003etrue\u003c/devMode\u003e\n  \u003c/configuration\u003e\n\u003c/plugin\u003e\n```\n\nYou can also execute following command in the root of your project or module without adding the above plugin:\n\n```shell\nmvn org.devocative.artemis:artemis-maven-plugin:1.5:run -DbaseUrl=http://localhost:8080/api -DdevMode=true\n```\n\n### Output\n\nArtemis uses the most common way to output the result: log files. During execution, Artemis creates a `logs` directory\nand flush all log files such as `artemis.log` in this directory. The log files will be rotated based on the time and\nsize. At the end of successful execution, a statistical summary is illustrated.\n\n```text\n2021-09-28 10:04:53,917 INFO  - *---------*---------*\n2021-09-28 10:04:53,930 INFO  - |   A R T E M I S   |\n2021-09-28 10:04:53,931 INFO  - *---------*---------*\n2021-09-28 10:04:54,028 INFO  - [Groovy] - Password: main=zhkcn enc=emhrY24\n2021-09-28 10:04:54,064 INFO  - Global Var: name=[cell] value=[09735929444]\n2021-09-28 10:04:54,075 INFO  - Global Var: name=[firstName] value=[zevm]\n2021-09-28 10:04:54,085 INFO  - Global Var: name=[lastName] value=[ttgl]\n2021-09-28 10:04:54,086 INFO  - =============== [RegisterRestaurateur] ===============\n2021-09-28 10:04:54,119 INFO  - --------------- [step #1] ---------------\n2021-09-28 10:04:54,135 INFO  - RQ: GET - http://localhost:8080/api/restaurateurs/registrations/09735929444\n2021-09-28 10:04:54,170 INFO  - RS: GET (200) - /api/restaurateurs/registrations/09735929444 [34 ms]\n  ContentType: null\n  \n2021-09-28 10:04:54,183 INFO  - --------------- [step #2] ---------------\n2021-09-28 10:04:54,192 INFO  - RQ: GET - http://localhost:8080/api/j4d/registrations/09735929444\n2021-09-28 10:04:54,199 INFO  - RS: GET (200) - /api/j4d/registrations/09735929444 [6 ms]\n  ContentType: application/json\n  {\"code\":\"3867\"}\n2021-09-28 10:04:54,232 INFO  - RS Properties = [code]\n2021-09-28 10:04:54,236 INFO  - --------------- [register] ---------------\n2021-09-28 10:04:54,274 INFO  - RQ: POST - http://localhost:8080/api/restaurateurs/registrations\n{\n    \"firstName\": \"zevm\",\n    \"lastName\": \"ttgl\",\n    \"cell\": \"09735929444\",\n    \"code\": \"3867\",\n    \"password\": \"emhrY24\"\n}\n2021-09-28 10:04:54,370 INFO  - RS: POST (200) - /api/restaurateurs/registrations [96 ms]\n  ContentType: application/json\n  {\"userId\":2,\"token\":\"eyJhbGciOiJIUzUxMiJ9.eyJ1aWQiOjIsInN1YiI6IjA5NzM1OTI5NDQ0Iiwicm9sZSI6IlJlc3RhdXJhdGV1ciIsImV4cCI6MTYzMjgxNDQ5NCwiaWF0IjoxNjMyODEwODk0fQ.tEWlnWC908zOj4tdAC0UeS_u8in4JoadjZ2YfIfH3vx5BRSoPGL6F33_wziTFxNQV_2W-LqYYjetBXN-ylvIDg\"}\n2021-09-28 10:04:54,382 INFO  - RS Properties = [userId, token]\n2021-09-28 10:04:54,386 INFO  - AssertRs Call: assertRs_register(Context, Map)\n2021-09-28 10:04:54,401 INFO  - --------------- [step #4] ---------------\n2021-09-28 10:04:54,422 INFO  - RQ: GET - http://localhost:8080/api/restaurateurs/2\nHEADERS = {Authorization=eyJhbGciOiJIUzUxMiJ9.eyJ1aWQiOjIsInN1YiI6IjA5NzM1OTI5NDQ0Iiwicm9sZSI6IlJlc3RhdXJhdGV1ciIsImV4cCI6MTYzMjgxNDQ5NCwiaWF0IjoxNjMyODEwODk0fQ.tEWlnWC908zOj4tdAC0UeS_u8in4JoadjZ2YfIfH3vx5BRSoPGL6F33_wziTFxNQV_2W-LqYYjetBXN-ylvIDg}\n2021-09-28 10:04:54,428 INFO  - RS: GET (200) - /api/restaurateurs/2 [6 ms]\n  ContentType: application/json\n  {\"firstName\":\"zevm\",\"lastName\":\"ttgl\",\"cell\":\"09735929444\",\"email\":null,\"id\":2,\"createdBy\":\"anonymous\",\"createdDate\":{\"year\":1400,\"month\":7,\"day\":6},\"lastModifiedBy\":\"anonymous\",\"lastModifiedDate\":{\"year\":1400,\"month\":7,\"day\":6},\"version\":0}\n2021-09-28 10:04:54,438 INFO  - RS Properties = [firstName, lastName, cell, email, id, createdBy, createdDate, lastModifiedBy, lastModifiedDate, version]\n2021-09-28 10:04:54,442 INFO  - ***** [STATISTICS] *****\n2021-09-28 10:04:54,443 INFO  - ID                             URI                                           Method  Status  Duration\n2021-09-28 10:04:54,443 INFO  - RegisterRestaurateur.step #1   /api/restaurateurs/registrations/09735929444  GET     200     34\n2021-09-28 10:04:54,443 INFO  - RegisterRestaurateur.step #2   /api/j4d/registrations/09735929444            GET     200     6\n2021-09-28 10:04:54,443 INFO  - RegisterRestaurateur.register  /api/restaurateurs/registrations              POST    200     96\n2021-09-28 10:04:54,443 INFO  - RegisterRestaurateur.step #4   /api/restaurateurs/2                          GET     200     6\n2021-09-28 10:04:54,443 INFO  - ***** [PASSED SUCCESSFULLY in 512 ms] *****\n```\n\n## Description of the XML and Groovy Files\n\nThe `\u003cvars/\u003e` are defining some variables for providing initial data for the test. In the `value`, you can call a\nfunction in the Groovy file by placing `${_.FUNCTION(PARAMS)}` in your attribute like the `cell` variable (the `_` is a\nvariable in Artemis's Groovy integration which refers to the object of `Script`\ncreated from the Groovy file).\n\nThe XML file consists of one or more `\u003cscenario\u003e`. The given name for the scenario is necessary. Each scenario has one\nor more HTTP request(s). The tags `\u003cget\u003e`, `\u003cpost\u003e`, `\u003cput\u003e`, `\u003cpatch\u003e`, and `\u003cdelete\u003e` are defining an HTTP request as\nthe tag name is the HTTP method. The `id` attribute assigns an identifier to the request, which is not required but\nhighly recommended. Most of the conventions in the Artemis is based on this `id`. For example, you can refer to the\nresponse body of a request via its `id`, such as `${register.rs.token}` used as a header parameter in the latest `\u003cget\u003e`\nrequest.\n\nFor `url`, you should set the resource part without the server or common prefix part of the URL. In the above example,\nthe final url for `register` request is `http://localhost:8080/api/restaurateurs/registrations` (\nthe `http://localhost:8080/api` part is passed as `baseUrl`).\n\nAfter sending the request, the result can be asserted in `\u003casserRs\u003e` tag. The `status` attribute has the expected value\nfor the http response code. Since Artemis supposes your response is an JSON object by default, this JSON object should\nhave the list of `properties`.\n\n## Parallel Execution and Performance Test\n\nYou can do performance test with Artemis. Just set the `parallel` config property with a number greater than 1. Then\nArtemis executes the XML file concurrently, and creates a thread for each execution. For each thread, a log\nfile `artemis-th-NN.log` is created, and all the execution output is written in that log file. At the end a\ncomprehensive statistical report is published in `artemis.log` file such as the following one:\n\n```text\n2021-09-28 10:09:34,212 INFO  - ID                             Status  Avg     Count  Min  Min(th)          Max  Max(th)\n2021-09-28 10:09:34,213 INFO  - RegisterRestaurateur.step #1   200     24.00   5      2    [artemis-th-03]  88   [artemis-th-01]\n2021-09-28 10:09:34,213 INFO  - RegisterRestaurateur.step #2   200     7.00    5      5    [artemis-th-05]  10   [artemis-th-01]\n2021-09-28 10:09:34,213 INFO  - RegisterRestaurateur.register  200     180.80  5      117  [artemis-th-03]  242  [artemis-th-01]\n2021-09-28 10:09:34,213 INFO  - RegisterRestaurateur.step #4   200     9.00    5      5    [artemis-th-03]  18   [artemis-th-05]\n```\n\n## Start Developing\n\nYou can create both XML and Groovy files by calling the following maven command in the root of your project or module:\n\n```shell\nmvn org.devocative.artemis:artemis-maven-plugin:1.5:create\n```\n\nAfter successful execution, the two files `artemis.xml` and `artemis.groovy`, are generated in `src/test/resources`\ndirectory.\n\n**Note:** To alter the generation, you can append `-Dname=NAME` to the command to generate files as `NAME.xml`\nand `NAME.groovy`.\n\n### `Artemis` Utility/Helper Class\n\nDuring test development, you need some actions such as random data generation, object \u003c-\u003e JSON conversion, and even data\nencryption/decryption. The `Artemis` class is a utility/helper class with some static methods that provides such\nfunctionalities. You can use it in XML by calling such `${Artemis.FUN()}` expression or directly in groovy.\n\n#### Random Data Generation Functions\n\n| Function                                               | Description                                                                                      |\n|--------------------------------------------------------|--------------------------------------------------------------------------------------------------|\n| `generate(len: int, charSet: List\u003cString\u003e...): String` | Generates random string of `len` characters of `charSet`, e.g. `generate(5, 'a'..'z', '1'..'9')` |\n| `generate(len: int): String`                           | Generates random string of `len` alpha-numeric characters                                        |\n| `rand(min: int, max: int): int`                        | Generates random number between `min` and `max` value                                            |\n| `uuid(): String`                                       | Generates UUID using `java.util.UUID`                                                            |\n\n#### String Functions\n\n| Function                                          | Description                                                                                   |\n|---------------------------------------------------|-----------------------------------------------------------------------------------------------|\n| `format(number: Number, pattern: String): String` | Format `number` based on `pattern` such as `#,###.##` using `DecimalFormat` class             |\n| `format(date: Date, pattern: String): String`     | Format `date` based on `pattern` such as `yyyy/MM/mm HH:mm:ss` using `SimpleDateFormat` class |\n| `jsonify(obj: Object): String`                    | Convert general `obj` to JSON string                                                          |\n| `objectify(json: String): Object`                 | Convert `json` string to a general object                                                     |\n| `log(msg: String)`                                | Log `msg` to main log process                                                                 |\n\n#### I/O Functions\n\n| Function                         | Description                                                                                      |\n|----------------------------------|--------------------------------------------------------------------------------------------------|\n| `readFile(name: String): String` | Read file `name` as string file. The relative file address is calculated due to Artemis XML file |\n| `http(): HttpBuilder`            | Create a `HttpBuilder` object to build a HTTP request                                            |\n\n```java\ndef rs=Artemis.http()\n\t.get(url:String) | .post(url:String) | .put(url:String) | .patch(url:String) | .delete(url:String)\n\t.header(key:String, value:String)         // optional, may call it many times\n\t.body(text:String) | .body(obj:Object)    // optional, may call it once\n\t.send()\n\n// Respons attributes\n\trs\n\t.code           // HTTP status code\n\t.codsString     // HTTP status code as string\n\t.contentType    // HTTP response content type\n\t.cookies        // Sent Cookies\n\t.body           // Response body as string\n\t.bodyAsObject   // Converted sent JSON-string body to an object\n```\n\n#### Encryption/Decryption Functions\n\n| Function                         | Description   |\n|----------------------------------|---------------|\n| `encBase64(str: String): String` | Encode base64 |\n| `decBase64(str: String): String` | Decode base64 |\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbizhani%2Fartemis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmbizhani%2Fartemis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbizhani%2Fartemis/lists"}