{"id":21179053,"url":"https://github.com/ocadotechnology/test-arranger","last_synced_at":"2025-07-09T22:32:04.078Z","repository":{"id":37414676,"uuid":"276046576","full_name":"ocadotechnology/test-arranger","owner":"ocadotechnology","description":"Arranges test data as fully populated objects","archived":false,"fork":false,"pushed_at":"2024-10-28T08:45:15.000Z","size":376,"stargazers_count":23,"open_issues_count":10,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-28T10:48:10.207Z","etag":null,"topics":["java","junit","kotlin","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/ocadotechnology.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":"2020-06-30T08:50:06.000Z","updated_at":"2024-10-28T08:45:19.000Z","dependencies_parsed_at":"2023-02-18T17:35:19.127Z","dependency_job_id":"ac8dec10-6fbc-4093-b646-b34b4eed4936","html_url":"https://github.com/ocadotechnology/test-arranger","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocadotechnology%2Ftest-arranger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocadotechnology%2Ftest-arranger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocadotechnology%2Ftest-arranger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocadotechnology%2Ftest-arranger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ocadotechnology","download_url":"https://codeload.github.com/ocadotechnology/test-arranger/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225600754,"owners_count":17494698,"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","kotlin","testing"],"created_at":"2024-11-20T17:28:09.221Z","updated_at":"2025-07-09T22:32:04.066Z","avatar_url":"https://github.com/ocadotechnology.png","language":"Java","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"***\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./logo.png\"\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003cb\u003e\u003cem\u003eTest Arranger\u003c/em\u003e\u003c/b\u003e\u003cbr\u003e\n\n[![Apache 2 license](https://img.shields.io/badge/license-Apache%202-blue?style=flat)](http://www.apache.org/licenses/)\n[![Build Status](https://github.com/ocadotechnology/test-arranger/actions/workflows/maven-verify.yml/badge.svg)](https://github.com/ocadotechnology/test-arranger/actions)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.ocadotechnology.gembus/test-arranger/badge.svg?style=flat)](https://repo1.maven.org/maven2/com/ocadotechnology/gembus/test-arranger/1.1/)\n[![Javadocs](http://www.javadoc.io/badge/com.ocadotechnology.gembus/test-arranger.svg)](https://www.javadoc.io/doc/com.ocadotechnology.gembus/test-arranger/latest/index.html)\n\n\u003c/div\u003e\n\n***\n\nIn TDD there are 3 phases: arrange, act and assert (given, when, then in BDD).\nThe assert phase has great tool support, you may be familiar with AssertJ, FEST-Assert or Hamcrest.\nIt is in contrast to the arrange phase.\nWhile arranging test data is often challenging and significant part of the test is typically devoted to it, it is hard to point out a tool that supports it. \n\nTest Arranger tries to fulfill this gap by arranging instances of classes required for tests.\nThe instances are filled with pseudo-random values that simplify the process of test data creation.\nThe tester only declares types of the required objects and gets brand new instances.\nWhen a pseudo-random value for a given field is not good enough, only this field must be set manually:\n\n```java\nProduct product = Arranger.some(Product.class);\nproduct.setBrand(\"Ocado\");\n```\n### Maven\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.ocadotechnology.gembus\u003c/groupId\u003e\n  \u003cartifactId\u003etest-arranger\u003c/artifactId\u003e\n  \u003cversion\u003e1.6.4\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle\n\n```groovy\ntestImplementation 'com.ocadotechnology.gembus:test-arranger:1.6.4'\n```\n\n## Features\n\n### Arranger\n\nThe Arranger class has several static methods for generating pseudo-random values of simple types.\nEach of them has a wrapping function to make the calls simpler for Kotlin.\nSome of the possible calls are listed below:\n\n|Java|Kotlin| result                                                                                                                                                                                                                                                                                                                                                                                                                     |\n|----|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|```Arranger.some(Product.class)```|```some\u003cProduct\u003e()```| an instance of Product with all fields filled with values                                                                                                                                                                                                                                                                                                                                                                  |\n|```Arranger.some(Product.class, \"brand\")```|```some\u003cProduct\u003e(\"brand\")```| an instance of Product without value for the brand field                                                                                                                                                                                                                                                                                                                                                                   |\n|```Arranger.someSimplified(Category.class)```|```someSimplified\u003cCategory\u003e()```| an instance of Category, fields of type collection has size reduced to 1 and depth for objects tree is limited to 3                                                                                                                                                                                                                                                                                                        |\n|```Arranger.someObjects(Product.class, 7)```|```someObjects\u003cProduct\u003e(7)```| a stream of size 7 of instances of Product                                                                                                                                                                                                                                                                                                                                                                                 |\n|```Arranger.someEmail()```|```someEmail()```| a string containing email address                                                                                                                                                                                                                                                                                                                                                                                          |\n|```Arranger.someLong()```|```someLong()```| a pseudo random number of type long                                                                                                                                                                                                                                                                                                                                                                                        |\n|```Arranger.someFrom(listOfCategories)```|```someFrom(listOfCategories)```| an entry form the listOfCategories                                                                                                                                                                                                                                                                                                                                                                                         |\n|```Arranger.someText()```|```someText()```| a string generated from a Markov Chain; by default, it is a very simple chain, but it can be reconfigured by putting other 'enMarkovChain' file on the test classpath with alternative definition, you can find one trained on an english corpus [here](https://github.com/ocadotechnology/test-arranger/releases/download/v1.1/enMarkovChain.zip); consult the included in the project 'enMarkovChain' file for the file format |\n| - |```some\u003cProduct\u003e {name = \"not so random\"}```| an instance of Product with all fields filled with random values except for `name` which is set to \"not so random\", this syntax can be used to set as many fields of the object as necessary, but each of the objects must be mutable                                                                                                                                                                                      |\n\n### Adjusting the arranged data\n\nCompletely random data may be not suitable for every test case. \nOften there is at least one field that is crucial for the test goal and needs a certain value. \nWhen the arranged class is mutable, or it is a Kotlin data class, or there is a way to create an altered copy (e.g. [Lombok's @Builder(toBuilder = true)](https://projectlombok.org/features/Builder)) then just use what is available.\nFortunately, even if it is unadjustable, you can use the Test Arranger. \nThere are dedicated versions of the ```some()``` and ```someObjects()``` methods that accept a parameter of type ```Map\u003cString,Supplier\u003e```.\nThe keys in this map represent field names whilst the corresponding suppliers deliver values that Test Arranger will set for you on those fields, e.g.:\n```java\nProduct product = Arranger.some(Product.class, Map.of(\"name\", () -\u003e value));\n```\n\n### Custom Arrangers\n\nBy default, the random values are generated according to the field type.\nRandom values not always correspond well with class invariants.\nWhen an entity always needs to be arranged concerning some rules regarding values of fields you may provide a custom arranger:\n```java\nclass ProductArranger extends CustomArranger\u003cProduct\u003e {\n    @Override\n    protected Product instance() {\n        Product product = enhancedRandom.nextObject(Parent.class);\n        product.setPrice(BigDecimal.valueOf(Arranger.somePositiveLong(9_999L)));\n        return product;\n    }\n}\n```\nTo have control over the process of instantiating ```Product``` we need to override the ```instance()``` method.\nInside the method we can create the instance of ```Product``` however we want.\nSpecifically, we may generate some random values.\nFor convenience, we have there a ```enhancedRandom``` field in the ```CustomArranger``` class.\nIn the given example, we generate an instance of ```Product``` with all fields having pseudo-random values, but then we change the price to something acceptable in our domain.\nThat is not negative and smaller than the 10k number.\n\nThe ```ProductArranger``` is automatically (using reflection) picked up by Arranger and used whenever new instance of ```Product``` is requested.\nIt not only regards direct calls like ```Arranger.some(Product.class)```, but also indirect.\nAssuming there is class ```Shop``` with field ```products``` of type ```List\u003cProduct\u003e```.\nWhen calling ```Arranger.some(Shop.class)```, the arranger will use ```ProductArranger``` to create all the products stored in ```Shop.products```.\n\n### Properties\nThe behaviour of the test-arranger can be configured using properties.\nIf you create `arranger.properties` file and save it in the root of classpath (usually that will be `src/test/resources/` directory), it will be picked up and the following properties will be applied:\n\n* `arranger.root`\n  The custom arrangers are picked up using reflection.\n  All classes extending ```CustomArranger``` are considered to be custom arrangers.\n  The reflection is focused on a certain package which by default is `com.ocado`.\n  That not necessarily is convenient for you.\n  However, with `arranger.root=your_package` it can be changed to `your_package`.\n  Try to have the package as specific as possible as having something to generic (e.g. just `com` which is root package in many libraries) will result in scanning hundreds of classes which will take noticeable time.\n* `arranger.randomseed`\n  By default, always the same seed is used to initialize the underlying pseudorandom values generator.\n  As a consequence, the subsequent executions will generate the same values.\n  To achieve randomness across runs, i.e. to always start with other random values, setting `arranger.randomseed=true` is necessary.\n* `arranger.cache.enable`\n  The process of arranging random instances requires some time. \n  If you create a large number of instances and you do not need them to be completely random, enabling the cache may be the way to go.\n  When enabled, the cache stores reference to each random instance and at some point the test-arranger stops creating new ones and instead reuses the cached instances.\n  By default the cache is disabled.\n* `arranger.overridedefaults`\n  Test-arranger respects the default field initialization, i.e. when there is a field initialized with an empty string, the instance returned by test-arranger has the empty string in this field.\n  Not always it is what you need in the tests, especially, when there is a convention in the project to initialize fields with empty values.\n  Fortunately, you can force test-arranger to overwrite the defaults with random values.\n  Set `arranger.overridedefaults` to true to override the default initialization.\n* `arranger.maxRandomizationDepth`\n  Some test data structures can generate any length chains of objects that reference each other. \n  However, to effectively use them in a test case, it's crucial to control the length of these chains. \n  By default, Test-arranger stops creating new objects at the 4th level of nesting depth. \n  If this default setting does not suit your project test cases, it can be adjusted using this parameter.\n* `arranger.android.customArrangers`\n  In Android Tests, the JVM has certain limitations - specifically, it may not automatically detect custom arrangers.\n  In such cases, this property comes to the rescue: you can list all your custom arrangers there.\n  It should be a comma-separated list of their canonical class names.\n  This ensures that test-arranger registers them regardless of any reflection related limitations.\n\n### Data.copy\n\nWhen you have a Java record that could be used as test data, but you need to change one or two of its fields, the `Data` class with its copy method provides a solution.\nThis is particularly useful when dealing with immutable records that don't have an obvious way to alter their fields directly.\n\nThe `Data.copy` method allows you to create a shallow copy of a record while selectively modifying the desired fields.\nBy providing a map of field overrides, you can specify the fields that need to be altered and their new values. \nThe copy method takes care of creating a new instance of the record with the updated field values.\n\nThis approach saves you from manually creating a new record object and setting the fields individually, providing a convenient way to generate test data with slight variations from existing records.\n\nOverall, the Data class and its copy method rescue the situation by enabling the creation of shallow copies of records with selected fields altered, providing flexibility and convenience when working with immutable record types:\n```java\nData.copy(myRecord, Map.of(\"recordFieldName\", () -\u003e \"altered value\"));\n```\n\n## The challenges it solves\n\nWhen going through tests of a software project one seldom has the impression that it cannot be done better.\nIn the scope of arranging test data, there are two areas we are trying to improve with Test Arranger.\n\n### Tests readability\n\nTests are much easier to understand when knowing the intention of the creator, i.e why the test was written and what kind of issues it should detect.\nUnfortunately, it is not extraordinary to see tests having in the arrange (given) section statements like the following one:\n```java\nProduct product = Product.builder()\n    .withName(\"Some name\")\n    .withBrand(\"Some brand\")\n    .withPrice(new BigDecimal(\"12.99\"))\n    .withCategory(\"Water, Juice \u0026 Drinks / Juice / Fresh\")\n...\n    .build();\n```\nWhen looking at such code, it is hard to say which values are relevant for the test and which are provided only to satisfy some not-null requirements.\nIf the test is about the brand, why not write it like that:\n```java\nProduct product = Arranger.some(Product.class);\nproduct.setBrand(\"Some brand\");\n```\nNow it is obvious that the brand is important.\nLet's try to make one step further.\nThe whole test may look as follows:\n```java\n//arrange\nProduct product = Arranger.some(Product.class);\nproduct.setBrand(\"Some brand\");\n\n//act\nReport actualReport = sut.createBrandReport(Collections.singletonList(product))\n\n//assert\nassertThat(actualReport.getBrand).isEqualTo(\"Some brand\") \n```\nWe're testing now that the report was created for the \"Some brand\" brand.\nBut is that the goal?\nIt makes more sense to expect that the report will be generated for the same brand, the given product is assigned to.\nSo what we want to test is:\n```java\n//arrange\nProduct product = Arranger.some(Product.class);\n\n//act\nReport actualReport = sut.createBrandReport(Collections.singletonList(product))\n\n//assert\nassertThat(actualReport.getBrand).isEqualTo(product.getBrand()) \n```\nIn case the brand field is mutable and we're afraid the `sut` may modify it, we can store its value in a variable before going into act phase and later use it for the assertion.\nThe test will be longer, but the intention remains clear.\n\nIt is noteworthy that what we have just done is an application of Generated Value and to some extent Creation Method patterns described in *xUnit Test Patterns: Refactoring Test Code* by Gerard Meszaros.\n\n### Shotgun surgery\n\nHave you ever changed a tiny thing in production code and end up with errors in dozen of tests?\nSome of them reporting failing assertion, some maybe even refusing to compile.\nThis is a shotgun surgery code smell that just shot at your innocent tests.\nWell, maybe not so innocent as they could be designed differently, to limit the collateral damage caused by tiny change.\nLet's analyze it using an example.\nSuppose we have in our domain the following class:\n```java\nclass TimeRange{\n    private LocalDateTime start;\n    private long durationinMs;\n\n    public TimeRange(LocalDateTime start, long durationInMs) {\n        ...\n```\nand that it is used in many places.\nEspecially in the tests, without Test Arranger, using statements like this one: ```new TimeRange(LocalDateTime.now(), 3600_000L);```\nWhat will happen if for some important reasons we are being forced to change the class to:\n```java\nclass TimeRange {\n    private LocalDateTime start;\n    private LocalDateTime end;\n\n    public TimeRange(LocalDateTime start, LocalDateTime end) {\n        ...\n```\nIt is quite challenging to come up with a series of refactoring that transforms the old version to the new one without breaking all dependent tests.\nMore likely is a scenario where the tests are adjusted to the new API of the class one by one.\nThis means a lot of not exactly exciting work with many questions regarding the desired value of duration (should I carefully convert it to the ```end``` of LocalDateTime type or was it just a convenient random value).\nThe life would be much easier with Test Arranger.\nWhen in all places requiring just not null ```TimeRange``` we have ```Arranger.some(TimeRange.class)```, it is as good for the new version of ```TimeRange``` as it was for the old one.\nThat leaves us with those few cases requiring not random ```TimeRange```, but as we already use Test Arranger to reveal test intention, in each case we exactly know what value should be used for the ```TimeRange```.\n\nBut, that is not everything we can do to improve the tests.\nPresumably, we can identify some categories of the ```TimeRange``` instance, e.g. ranges from past, ranges from future and ranges currently active.\nThe ```TimeRangeArranger``` is a great place to arrange that:\n```java\nclass TimeRangeArranger extends CustomArranger\u003cTimeRange\u003e {\n\n    private final long MAX_DISTANCE = 999_999L;\n\n    @Override\n    protected TimeRange instance() {\n        LocalDateTime start = enhancedRandom.nextObject(LocalDateTime.class);\n        LocalDateTime end = start.plusHours(Arranger.somePositiveLong(MAX_DISTANCE));\n        return new TimeRange(start,end);\n    }\n\n    public TimeRange fromPast() {\n        LocalDateTime now = LocalDateTime.now();\n        LocalDateTime end = now.minusHours(Arranger.somePositiveLong(MAX_DISTANCE));\n        return new TimeRange(end.minusHours(Arranger.somePositiveLong(MAX_DISTANCE)), end);\n    }\n\n    public TimeRange fromFuture() {\n        LocalDateTime now = LocalDateTime.now();\n        LocalDateTime start = now.plusHours(Arranger.somePositiveLong(MAX_DISTANCE));\n        return new TimeRange(start, start.plusHours(Arranger.somePositiveLong(MAX_DISTANCE)));\n    }\n\n    public TimeRange currentlyActive() {\n        LocalDateTime now = LocalDateTime.now();\n        LocalDateTime start = now.minusHours(Arranger.somePositiveLong(MAX_DISTANCE));\n        LocalDateTime end = now.plusHours(Arranger.somePositiveLong(MAX_DISTANCE));\n        return new TimeRange(start, end);\n    }\n}\n```\nSuch a creation method should not be created upfront but rather correspond with existing test cases.\nNonetheless, there are chances that the ```TimeRangeArranger``` will cover all cases where instances of ```TimeRange``` are created for tests.\nAs a consequence, in place of constructor calls with several mysterious parameters, we have arranger with a well-named method explaining the domain meaning of the created object and helping in understanding test intention. \n\n## How to organize tests with Test Arranger\n\nWe identified two levels of test data creators when discussing the challenges solved by Test Arranger.\nTo make the picture complete we need to mention at least one more, that is the Fixtures.\nFor the sake of this discussion, we may assume that Fixture is a class designed to create complex structures of test data.\nThe custom arranger is always focused on one class, but sometimes you can observe in your test cases reoccurring constellations of two or more classes.\nThat may be User and his or her Bank account.\nThere may be a CustomArranger for each of them, but why ignore the fact that they often come together.\nThis is when we should start thinking about a Fixture.\nIt will be responsible for creating both User and the Bank account (presumably using dedicated custom arrangers) and linking them together.\nThe Fixtures are described in detail, including several implementation variants in *xUnit Test Patterns: Refactoring Test Code* by Gerard Meszaros.\n\nSo we have three types of building blocks in the test classes.\nEach of them can be considered to be the counterpart of a concept (Domain Driven Design building block) from production code:\n![](readme_class_diagram.jpg)\n\nOn the surface, there are primitives and simple objects.\nThat is something that appears even in the simplest unit tests.\nYou can cover arranging such test data with the `someXxx` methods from ```Arranger``` class.\n\nSo you may have services requiring tests that work solely on `User` instances or both `User` and other classes contained in the `User` class, like a list of addresses.\nTo cover such cases, typically a custom arranger is required, i.e. the `UserArranger`.\nIt will create instances of `User` respecting all constraints and class invariants.\nMoreover, it will pick up `AddressArranger`, when exists, to fill the list of addresses with valid data.\nWhen several test cases require a certain type of user, e.g. homeless users with an empty list of addresses, an additional method can be created in the UserArranger.\nAs a consequence, whenever it will be required to create a `User` instance for the tests, it will be enough to look into `UserArranger` and select an adequate factory method or just call `Arranger.some(User.class)`.\n\nThe most challenging case regards tests depending on large data structures.\nIn eCommerce that could be a shop containing many products, but also user accounts with shopping history.\nArranging data for such test cases is usually non-trivial and repeating such a thing wouldn't be wise.\nIt is much better to store it in a dedicated class under a well-named method, like `shopWithNineProductsAndFourCustomers`, and reuse in each of the tests.\nWe strongly recommend to use a naming convention for such classes, in order to make them easy to find, our suggestion is to use `Fixture` postfix.\nEventually, we may end up with something like this:\n```java\nclass ShopFixture {\n    Repository repo;\n    public void shopWithNineProductsAndFourCustomers() {\n      Arranger.someObjects(Product.class, 9)\n              .forEach(p -\u003e repo.save(p));\n\n      Arranger.someObjects(Customer.class, 4)\n              .forEach(p -\u003e repo.save(p));\n    }\n}\n```\n\n## Articles and blog posts\n\n* [On testing in DDD](https://medium.com/p/f250482b5717)\n* Test Arranger and the Second Law of Thermodynamics ([EN](https://www.youtube.com/watch?v=rAi2t05Om3Y#t=43s), [PL](https://www.youtube.com/watch?v=ulW0aRmDIuE))\n* [Arranging Java Records](https://medium.com/@marian.jureczko/arranging-java-records-d11ec9141fde)\n* [Flaky tests: the handy simplification](https://medium.com/@marian.jureczko/flaky-tests-the-handy-simplification-6e4296a0d71b)\n* [FAQ](https://medium.com/@marian.jureczko/test-arranger-the-asked-questions-a4ec99a0f742)\n\n## Versioning\n\nThe newest test-arranger version is compiled using Java 17 and should be used in Java 17+ runtime.\nHowever, there is also a Java 8 branch for backward compatibility, covered with the 1.4.x versions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focadotechnology%2Ftest-arranger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Focadotechnology%2Ftest-arranger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focadotechnology%2Ftest-arranger/lists"}