├── .forceignore ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── _config.yml ├── apex-test-kit └── main │ └── classes │ ├── ATK.cls │ ├── ATK.cls-meta.xml │ ├── ATKCore.cls │ ├── ATKCore.cls-meta.xml │ ├── ATKCoreTest.cls │ ├── ATKCoreTest.cls-meta.xml │ ├── ATKMock.cls │ ├── ATKMock.cls-meta.xml │ ├── ATKMockTest.cls │ ├── ATKMockTest.cls-meta.xml │ ├── ATKTest.cls │ └── ATKTest.cls-meta.xml ├── config └── project-scratch-def.json ├── docs ├── bdd.md └── images │ ├── deploy-button.png │ ├── mock-relationship.png │ └── sales-objects.png ├── package.json ├── scripts ├── apex │ ├── benchmark.apex │ ├── demo-campaign.apex │ ├── demo-cases.apex │ ├── demo-consumer.apex │ ├── demo-products.apex │ ├── demo-sales.apex │ └── demo-users.apex └── shell │ └── sfdx.sh └── sfdx-project.json /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore. 2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore 3 | # For useful gitignore templates see: https://github.com/github/gitignore 4 | 5 | # Salesforce cache 6 | .sf/ 7 | .sfdx/ 8 | .localdevserver/ 9 | deploy-options.json 10 | 11 | # LWC VSCode autocomplete 12 | **/lwc/jsconfig.json 13 | 14 | # LWC Jest coverage reports 15 | coverage/ 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # Dependency directories 25 | node_modules/ 26 | 27 | # Eslint cache 28 | .eslintcache 29 | 30 | # MacOS system files 31 | .DS_Store 32 | 33 | # Windows system files 34 | Thumbs.db 35 | ehthumbs.db 36 | [Dd]esktop.ini 37 | $RECYCLE.BIN/ 38 | 39 | # Local environment variables 40 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | **/staticresources/** 6 | .localdevserver 7 | .sfdx 8 | .vscode 9 | 10 | coverage/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "overrides": [ 4 | { 5 | "files": "*.{cls,apex}", 6 | "options": { "tabWidth": 4, "printWidth": 120 } 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly istate otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apex Test Kit 2 | 3 | ![](https://img.shields.io/badge/version-4.1-brightgreen.svg) ![](https://img.shields.io/badge/build-passing-brightgreen.svg) ![](https://img.shields.io/badge/coverage-97%25-brightgreen.svg) 4 | 5 | Apex Test Kit can help generate massive data for Apex test classes, including mock sObjects with read-only fields. It solves two pain points during data creation: 6 | 7 | 1. Establish arbitrary levels of many-to-one, one-to-many relationships. 8 | 2. Generate field values based on simple rules automatically. 9 | 10 | It can also help generate method stubs with the help of Apex `StubProvider` interface underneath. 11 | 12 | 1. Stubs are defined and verified with BDD given-when-then styles. 13 | 2. [Strict mode](https://github.com/apexfarm/ApexTestKit/wiki/Apex-Test-Kit-with-BDD#1-1-strict-mode) is enforced by default to help developers write clean mocking codes and increase productivity. 14 | 15 | | Environment | Installation Link | Version | 16 | | --------------------- | ------------------------------------------------------------ | ------- | 17 | | Production, Developer | | ver 4.1 | 18 | | Sandbox | | ver 4.1 | 19 | 20 | --- 21 | 22 | ### **v4.x Release Notes** 23 | 24 | #### Major Changes 25 | 26 | - **v4.0 Feature**: Ported Mockito BDD Features 27 | - **v4.1 Enhancement**: `ATK.SaveResult` now supports getting list of Ids. 28 | 29 | #### Next Steps 30 | 31 | - Performance tuning for the BDD features. 32 | - Enhance the BDD features. 33 | - Support `HttpCalloutMock` in BDD style. 34 | 35 | --- 36 | 37 | ## 🔥 Apex Test Kit with BDD 38 | 39 | Please check the developer guide at this [wiki page](https://github.com/apexfarm/ApexTestKit/wiki/Apex-Test-Kit-with-BDD). 40 | 41 | ```java 42 | YourClass mock = (YourClass) ATK.mock(YourClass.class); 43 | // Given 44 | ATK.startStubbing(); 45 | ATK.given(mock.doSomething()).willReturn('Sth.'); 46 | ATK.stopStubbing(); 47 | 48 | // When 49 | String returnValue = mock.doSomething(); 50 | 51 | // Then 52 | System.assertEquals('Sth.', returnValue); 53 | ((ATKMockTest) ATK.then(mock).should().once()).doSomething(); 54 | ``` 55 | 56 | ## Table of Contents 57 | 58 | - [Introduction](#introduction) 59 | - [Performance](#performance) 60 | - [Demos](#demos) 61 | - [Relationship](#relationship) 62 | - [One to Many](#one-to-many) 63 | - [Many to One](#many-to-one) 64 | - [Many to Many](#many-to-many) 65 | - [Many to Many with Junction](#many-to-many-with-junction) 66 | - [📥Save](#save) 67 | - [Command API](#command-api) 68 | - [Save Result API](#save-result-api) 69 | - [☕Mock](#-mock) 70 | - [Mock with Children](#mock-with-children) 71 | - [Mock with Predefined List](#mock-with-predefined-list) 72 | - [Fake Id](#fake-id) 73 | - [Entity Keywords](#entity-keywords) 74 | - [Entity Creation Keywords](#entity-creation-keywords) 75 | - [Entity Updating Keywords](#entity-updating-keywords) 76 | - [Entity Reference Keywords](#entity-reference-keywords) 77 | - [Field Keywords](#field-keywords) 78 | - [Basic Field Keywords](#basic-field-keywords) 79 | - [Arithmetic Field Keywords](#arithmetic-field-keywords) 80 | - [Lookup Field Keywords](#lookup-field-keywords) 81 | - [Entity Builder Factory](#entity-builder-factory) 82 | - [License](#license) 83 | 84 | ## Introduction 85 | 86 |

87 | Sales Object Graph 88 |

89 | 90 | Imagine the complexity to generate all sObjects and relationships in the above diagram. With ATK we can create them within just one Apex statement. Here, we are generating: 91 | 92 | 1. _200_ accounts with names: `Name-0001, Name-0002, Name-0003...` 93 | 2. Each of the accounts has _2_ contacts. 94 | 3. Each of the contacts has _1_ opportunity via the OpportunityContactRole. 95 | 4. Also each of the accounts has _2_ orders. 96 | 5. Also each of the orders belongs to _1_ opportunity from the same account. 97 | 98 | ```java 99 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 200) 100 | .field(Account.Name).index('Name-{0000}') 101 | .withChildren(Contact.SObjectType, Contact.AccountId, 400) 102 | .field(Contact.LastName).index('Name-{0000}') 103 | .field(Contact.Email).index('test.user+{0000}@email.com') 104 | .field(Contact.MobilePhone).index('+86 186 7777 {0000}') 105 | .withChildren(OpportunityContactRole.SObjectType, OpportunityContactRole.ContactId, 400) 106 | .field(OpportunityContactRole.Role).repeat('Business User', 'Decision Maker') 107 | .withParents(Opportunity.SObjectType, OpportunityContactRole.OpportunityId, 400) 108 | .field(Opportunity.Name).index('Name-{0000}') 109 | .field(Opportunity.ForecastCategoryName).repeat('Pipeline') 110 | .field(Opportunity.Probability).repeat(0.9, 0.8) 111 | .field(Opportunity.StageName).repeat('Prospecting') 112 | .field(Opportunity.CloseDate).addDays(Date.newInstance(2020, 1, 1), 1) 113 | .field(Opportunity.TotalOpportunityQuantity).add(1000, 10) 114 | .withParents(Account.SObjectType, Opportunity.AccountId) 115 | .also(4) 116 | .withChildren(Order.SObjectType, Order.AccountId, 400) 117 | .field(Order.Name).index('Name-{0000}') 118 | .field(Order.EffectiveDate).addDays(Date.newInstance(2020, 1, 1), 1) 119 | .field(Order.Status).repeat('Draft') 120 | .withParents(Contact.SObjectType, Order.BillToContactId) 121 | .also() 122 | .withParents(Opportunity.SObjectType, Order.OpportunityId) 123 | .save(); 124 | ``` 125 | 126 | **Note**: `withChildren()` and `withParents()` without a third size parameter indicate they will back reference the sObjects created previously in the statement. 127 | 128 | ### Performance 129 | 130 | The scripts used to perform benchmark testing are documented under `scripts/apex/benchmark.apex`. The results are averages of three successive insertions of 1000 accounts under the following conditions: 131 | 132 | 1. No duplicate rules, process builders, and triggers etc. 133 | 2. Debug level is set to DEBUG. 134 | 135 | | 1000 \* Account | Database.insert | ATK Save | ATK Mock | ATK Mock Perf. | 136 | | --------------- | --------------- | -------- | -------- | -------------- | 137 | | CPU Time | 2005 | 2304 | 1103 | ~2x faster | 138 | | Real Time (ms) | 5672 | 5584 | 869 | ~6.5x faster | 139 | 140 | ### Demos 141 | 142 | Here are demos under the `scripts/apex` folder, they have been successfully run in fresh Salesforce organization with appropriate feature enabled. If not, please try to fix them with FLS, validation rules or duplicate rules etc. 143 | 144 | | Subject | File Path | Description | 145 | | -------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | 146 | | Campaign | `scripts/apex/demo-campaign.apex` | How to genereate campaigns with hierarchy relationships. `ATK.EntityBuilder` is implemented to reuse the field population logic. | 147 | | Consumer Goods Cloud | `scripts/apex/demo-consumer.apex` | Create meaningful sObject relationship distributions in one ATK statement. | 148 | | Sales | `scripts/apex/demo-sales.apex` | You've already seen it in the above paragraph. | 149 | | Products | `scripts/apex/demo-products.apex` | How to generate PriceBook2, PriceBookEntry, Product2, ProductCategory, Catalog. | 150 | | Cases | `scripts/apex/demo-cases.apex` | How to generate Accounts, Contacts and Cases. | 151 | | Users | `scripts/apex/demo-users.apex` | How to generate community users in one goal. | 152 | 153 | ## Relationship 154 | 155 | The object relationships described in a single ATK statement must be a Directed Acyclic Graph ([DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph)) , thus no cyclic relationships. If the validation fails, an exception will be thrown. 156 | 157 | ### One to Many 158 | 159 | ```java 160 | ATK.prepare(Account.SObjectType, 10) 161 | .field(Account.Name).index('Name-{0000}') 162 | .withChildren(Contact.SObjectType, Contact.AccountId, 20) 163 | .field(Contact.LastName).index('Name-{0000}') 164 | .save(); 165 | ``` 166 | 167 | | Account Name | Contact Name | 168 | | ------------ | ------------ | 169 | | Name-0001 | Name-0001 | 170 | | Name-0001 | Name-0002 | 171 | | Name-0002 | Name-0003 | 172 | | Name-0002 | Name-0004 | 173 | | ... | ... | 174 | 175 | ### Many to One 176 | 177 | The result of the following statement is identical to the one above. 178 | 179 | ```java 180 | ATK.prepare(Contact.SObjectType, 20) 181 | .field(Contact.LastName).index('Name-{0000}') 182 | .withParents(Account.SObjectType, Contact.AccountId, 10) 183 | .field(Account.Name).index('Name-{0000}') 184 | .save(); 185 | ``` 186 | 187 | ### Many to Many 188 | 189 | ```java 190 | ATK.prepare(Opportunity.SObjectType, 10) 191 | .field(Opportunity.Name).index('Opportunity {0000}') 192 | .withChildren(OpportunityContactRole.SObjectType, OpportunityContactRole.OpportunityId, 20) 193 | .field(OpportunityContactRole.Role).repeat('Business User', 'Decision Maker') 194 | .withParents(Contact.SObjectType, OpportunityContactRole.ContactId, 10) 195 | .field(Contact.LastName).index('Contact {0000}') 196 | .mock(); 197 | ``` 198 | 199 | The above ATK statement will give the following distribution patterns, which seems not intuitive, if not intentional. It only makes scenes when the same contact play two different roles in the same opportunity. 200 | 201 | | Opportunity Name | Contact Name | Contact Role | 202 | | ---------------- | ------------ | -------------- | 203 | | Opportunity 0001 | Contact 0001 | Business User | 204 | | Opportunity 0001 | Contact 0001 | Decision Maker | 205 | | Opportunity 0002 | Contact 0002 | Business User | 206 | | Opportunity 0002 | Contact 0002 | Decision Maker | 207 | | ... | ... | .... | 208 | 209 | ### Many to Many with Junction 210 | 211 | `junctionOf()` can annotate an sObject as the junction of a many-to-many relationship. Its main purpose is to distribute parents of the junction object from one branch to another in a specific order. Note: 212 | 213 | - `junctionOf()` must be used directly after [Entity Keywords](#entity-keywords). 214 | - All parent relationships of the junction sObject used in the statement must be listed in the `junctionOf()` keyword. 215 | - Different defining order of the junction relationships will result in different distributions. 216 | 217 | ```java 218 | ATK.prepare(Opportunity.SObjectType, 10) 219 | .field(Opportunity.Name).index('Opportunity {0000}') 220 | .withChildren(OpportunityContactRole.SObjectType, OpportunityContactRole.OpportunityId, 20) 221 | .junctionOf(OpportunityContactRole.OpportunityId, OpportunityContactRole.ContactId) 222 | .field(OpportunityContactRole.Role).repeat('Business User', 'Decision Maker') 223 | .withParents(Contact.SObjectType, OpportunityContactRole.ContactId, 10) 224 | .field(Contact.LastName).index('Contact {0000}') 225 | .mock(); 226 | ``` 227 | 228 | | Opportunity Name | Contact Name | Contact Role | 229 | | ---------------- | ------------ | -------------- | 230 | | Opportunity 0001 | Contact 0001 | Business User | 231 | | Opportunity 0001 | Contact 0002 | Decision Maker | 232 | | Opportunity 0002 | Contact 0003 | Business User | 233 | | Opportunity 0002 | Contact 0004 | Decision Maker | 234 | | ... | ... | .... | 235 | 236 | | Junction Keyword API | Description | 237 | | :-------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | 238 | | junctionOf(SObjectField _parentId1_, SObjectField _parentId2_); | Annotate an entity is a junction of the listed parent relationships. | 239 | | junctionOf(SObjectField _parentId1_, SObjectField _parentId2_, SObjectField _parentId3_); | Annotate an entity is a junction of the listed parent relationships. | 240 | | junctionOf(SObjectField _parentId1_, SObjectField _parentId2_, SObjectField _parentId3_, SObjectField _parentId4_); | Annotate an entity is a junction of the listed parent relationships. | 241 | | junctionOf(SObjectField _parentId1_, SObjectField _parentId2_, SObjectField _parentId3_, SObjectField _parentId4_, SObjectField _parentId5_); | Annotate an entity is a junction of the listed parent relationships. | 242 | | junctionOf(List\ _parentIds_); | Annotate an entity is a junction of the listed parent relationships. | 243 | 244 | ## 📥Save 245 | 246 | ### Command API 247 | 248 | There are three commands to create the sObjects, in database, in memory, or in mock istate. 249 | 250 | | Method API | Description | 251 | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 252 | | ATK.SaveResult save() | Actual DMLs will be performed to insert/update sObjects into Salesforce. | 253 | | ATK.SaveResult save(Boolean _doInsert_) | If `doInsert` is `false`, no actual DMLs will be performed, just in-memory generated SObjects will be returned. Only writable fields can be populated. | 254 | | ATK.SaveResult mock() | No actual DMLs will be performed, but sObjects will be returned in SaveResult as if they are newly retrieved by SOQL with fake Ids. Both writable and read-only fields can be populated | 255 | 256 | ### Save Result API 257 | 258 | Use `ATK.SaveResult` to retrieve sObjects generated from the ATK statement. 259 | 260 | | Method | Description | 261 | | ----------------------------------------------------------- | ------------------------------------------------------------ | 262 | | List get(SObjectType _objectType_) | Get the sObjects generated for the first `SObjectType` defined in ATK statement. | 263 | | List get(SObjectType _objectType_, Integer _nth_); | Get the sObjects generated for the nth `SObjectType` defined in ATK statement, i.e. child accounts in the Account Hierarchy. | 264 | | List getAll(SObjectType _objectType_) | Get all sObjects generated for `SObjectType` defined in ATK statement. | 265 | | List getIds(SObjectType _objectType_) | Get the sObject Ids generated for the first `SObjectType` defined in ATK statement. | 266 | | List getIds(SObjectType _objectType_, Integer _nth_); | Get the sObject Ids generated for the nth `SObjectType` defined in ATK statement, i.e. child accounts in the Account Hierarchy. | 267 | | List getAllIds(SObjectType _objectType_) | Get all sObject Ids generated for`SObjectType` defined in ATK statement. | 268 | 269 | ## ☕ Mock 270 | 271 | The followings are supported, when generate sObjects with `mock()` command: 272 | 273 | 1. Assign extremely large fake IDs to the generated sObjects. 274 | 2. Assign values to read-only fields, such as _formula fields_, _rollup summary fields_, and _system fields_. 275 | 3. Assign one level children relationship and multiple level parent relationships. 276 | 277 | ### Mock with Children 278 | 279 |

280 | Mock Relationship 281 | To establish a relationship graph as the picture on the right, we can start from any node. However only the sObjects created in the prepare statement can have child relationships referencing their direct children.

282 | Note: As the diagram illustrated the direct children D and E of B can not reference back to B. The decision is made to prevent a Known Salesforce Issue reported since winter 19. Here we are trying to avoid forming circular references. But D and E can still have other parent relationships, such as D to C.

283 | All the nodes in green are reachable from node B. The diagram can be interpreted as the following SOQL statement: 284 |

285 | 286 | ```SQL 287 | SELECT Id, A__r.Id, (SELECT Id FROM E__r), (SELECT Id, C__r.Id FROM D__r) FROM B__c 288 | ``` 289 | 290 | And we can generate them with the following ATK statement: 291 | 292 | ```java 293 | ATK.SaveResult result = ATK.prepare(B__c.SObjectType, 10) 294 | .withParents(A__c.SObjectType, B__c.A_ID__c, 10) 295 | .also() 296 | .withChildren(D__c.SObjectType, D__c.B_ID__c, 10) 297 | .withParents(C__c.SObjectType, D__c.C_ID__c, 10) 298 | .also() 299 | .withChildren(F__c.SObjectType, F__c.D_ID__c, 10) 300 | .also(2) 301 | .withChildren(E__c.SObjectType, E__c.B_ID__c, 10) 302 | .mock(); 303 | 304 | List listOfB = (List)result.get(B__c.SObjectType); 305 | for (B__c itemB : listOfB) { 306 | System.assertEquals(1, itemB.D__r.size()); 307 | System.assertEquals(1, itemB.E__r.size()); 308 | } 309 | ``` 310 | 311 | ### Mock with Predefined List 312 | 313 | Mock also supports predefined list or SOQL query results. But if there are any parent or child relationships in the predefined list, they are going to be trimmed in the generated mock sObjects. 314 | 315 | ```java 316 | List listOfB = [SELECT X__r.Id, (SELECT Id FROM Y__r) FROM B__c LIMIT 3]; 317 | 318 | ATK.SaveResult result = ATK.prepare(B__c.SObjectType, listOfB) 319 | .withParents(A__c.SObjectType, B__c.A_ID__c, 1) 320 | .also() 321 | .withChildren(D__c.SObjectType, D__c.B_ID__c, 6) 322 | .mock() 323 | 324 | List mockOfB = (List)result.get(B__c.SObjectType); 325 | // The B__c in mockOfB cannot reference X__r and Y__r any more. 326 | // The B__c in listOfB can still reference X__r and Y__r. 327 | ``` 328 | 329 | ### Fake Id 330 | 331 | These methods are exposed in case we need manually control the ID assignments such as: 332 | 333 | ```java 334 | Id fakeUserId = ATK.fakeId(User.SObjectType, 1); 335 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 9) 336 | .field(Account.OwnerId).repeat(fakeUserId) 337 | .mock() 338 | ``` 339 | 340 | | Keyword API | Description | 341 | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 342 | | Id fakeId(Schema.SObjectType _objectType_) | Return self incrementing fake IDs. They will start over from each transaction, which means they are unique within each transaction. By default Ids will start from `ATK.fakeId(Account.SObjectType, 1)`. | 343 | | Id fakeId(Schema.SObjectType _objectType_, Integer _index_) | Return the fake ID specified an index explicitly. | 344 | 345 | ## Entity Keywords 346 | 347 | These keywords are used to establish arbitrary levels of many-to-one, one-to-many relationships. Here is a dummy example to demo the use of Entity keywords. Each of them will start a new sObject context. And it is advised to use the following indentation for clarity. 348 | 349 | ```java 350 | ATK.prepare(A__c.SObjectType, 10) 351 | .withChildren(B__c.SObjectType, B__c.A_ID__c, 10) 352 | .withParents(C__c.SObjectType, B__c.C_ID__c, 10) 353 | .withChildren(D__c.SObjectType, D__c.C_ID__c, 10) 354 | .also() // Go back 1 depth to C__c 355 | .withChildren(E__c.SObjectType, E__c.C_ID__c, 10) 356 | .also(2) // Go back 2 depth to B__c 357 | .withChildren(F__c.SObjectType, F__c.B_ID__c, 10) 358 | .save(); 359 | ``` 360 | 361 | ### Entity Creation Keywords 362 | 363 | All the following APIs have an `Integer size` parameter at the end, which indicate how many records will be created on the fly. 364 | 365 | ```java 366 | ATK.prepare(A__c.SObjectType, 10) 367 | .withChildren(B__c.SObjectType, B__c.A_ID__c, 10) 368 | .withParents(C__c.SObjectType, B__c.C_ID__c, 10) 369 | .save(); 370 | ``` 371 | 372 | | Keyword API | Description | 373 | | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | 374 | | prepare(SObjectType _objectType_, Integer _size_) | Always start chain with `prepare()` keyword. It is the root sObject to start relationship with. | 375 | | withParents(SObjectType _objectType_, SObjectField _referenceField_, Integer _size_) | Establish many to one relationship between the previous working on sObject and the current sObject. | 376 | | withChildren(SObjectType _objectType_, SObjectField _referenceField_, Integer _size_) | Establish one to many relationship between the previous working on sObject and the current sObject. | 377 | 378 | ### Entity Updating Keywords 379 | 380 | All the following APIs have a `List objects` parameter at the end, which indicates the sObjects are selected/created elsewhere, and ATK will help to `upsert` them. 381 | 382 | ```java 383 | ATK.prepare(A__c.SObjectType, [SELECT Id FROM A__c]) // Select existing sObjects 384 | .field(A__c.Name).index('Name-{0000}') // Update existing sObjects 385 | .field(A__c.Price).repeat(100) 386 | .withChildren(B__c.SObjectType, B__c.A_ID__c, new List { 387 | new B__c(Name = 'Name-A'), // Manually assign field values 388 | new B__c(Name = 'Name-B'), 389 | new B__c(Name = 'Name-C')}) 390 | .field(B__c.Counter__c).add(1, 1) // Automatically assign field values 391 | .field(B__c.Weekday__c).repeat('Mon', 'Tue') // Automatically assign field values 392 | .withParents(C__c.SObjectType, B__c.C_ID__c, new List { 393 | new C__c(Name = 'Name-A'), 394 | new C__c(Name = 'Name-B'), 395 | new C__c(Name = 'Name-C')}) 396 | .save(); 397 | ``` 398 | 399 | | Keyword API | Description | 400 | | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | 401 | | prepare(SObjectType _objectType_, List\ _objects_) | Always start chain with `prepare()` keyword. It is the root sObject to start relationship with. | 402 | | withParents(SObjectType _objectType_, SObjectField _referenceField_, List\ _objects_) | Establish many to one relationship between the previous working on sObject and the current sObject. | 403 | | withChildren(SObjectType _objectType_, SObjectField _referenceField_, List\ _objects_) | Establish one to many relationship between the previous working on sObject and the current sObject. | 404 | 405 | ### Entity Reference Keywords 406 | 407 | All the following APIs don't have a third parameter of size or list at the end, which means the relationship will look back to reference the previously created sObjects. 408 | 409 | | Keyword API | Description | 410 | | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | 411 | | withParents(SObjectType _objectType_, SObjectField _referenceField_) | Establish many to one relationship between the previous working on sObject and the current sObject. | 412 | | withChildren(SObjectType _objectType_, SObjectField _referenceField_) | Establish one to many relationship between the previous working on sObject and the current sObject. | 413 | 414 | **Note**: Once these APIs are used, please make sure there are sObjects with the same type created previously, and only created once. 415 | 416 | ## Field Keywords 417 | 418 | These keywords are used to generate field values based on simple rules automatically. 419 | 420 | ```java 421 | ATK.prepare(A__c.SObjectType, 10) 422 | .withChildren(B__c.SObjectType, B__c.A_ID__c, 10) 423 | .field(B__C.Name__c).index('Name-{0000}') 424 | .field(B__C.PhoneNumber__c).index('+86 186 7777 {0000}') 425 | .field(B__C.Price__c).repeat(12.34) 426 | .field(B__C.CampanyName__c).repeat('Google', 'Apple', 'Microsoft') 427 | .field(B__C.Counter__c).add(1, 1) 428 | .field(B__C.StartDate__c).addDays(Date.newInstance(2020, 1, 1), 1) 429 | .save(); 430 | ``` 431 | 432 | ### Basic Field Keywords 433 | 434 | | Keyword API | Description | 435 | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | 436 | | index(String _format_) | Formatted string with `{0000}`, can recognize left padding. i.e. `Name-{0000}` will generate Name-0001, Name-0002, Name-0003 etc. | 437 | | **Repeat Family** | | 438 | | repeat(Object _value_) | Repeat with a single fixed value. | 439 | | repeat(Object _value1_, Object _value2_) | Repeat with the provided values alternatively. | 440 | | repeat(Object _value1_, Object _value2_, Object _value3_) | Repeat with the provided values alternatively. | 441 | | repeat(Object _value1_, Object _value2_, Object _value3_, Object _value4_) | Repeat with the provided values alternatively. | 442 | | repeat(Object _value1_, Object _value2_, Object _value3_, Object _value4_, Object _value5_) | Repeat with the provided values alternatively. | 443 | | repeat(List\ _values_) | Repeat with the provided values alternatively.\*\* | 444 | | **RepeatX Family** | | 445 | | repeatX(Object _value1_, Integer _size1_, Object _value2_, Integer _size2_) | repeat each value by x, y... times in sequence. | 446 | | repeatX(Object _value1_, Integer _size1_, Object _value2_, Integer _size2_, Object _value3_, Integer _size3_) | repeat each value by x, y... times in sequence. | 447 | | repeatX(Object _value1_, Integer _size1_, Object _value2_, Integer _size2_, Object _value3_, Integer _size3_, Object _value4_, Integer _size4_) | repeat each value by x, y... times in sequence. | 448 | | repeatX(Object _value1_, Integer _size1_, Object _value2_, Integer _size2_, Object _value3_, Integer _size3_, Object _value4_, Integer _size4_, Object _value5_, Integer _size5_) | repeat each value by x, y... times in sequence. | 449 | | repeatX(List\ _values_, List\ _sizes_) | repeat each value by x, y... times in sequence. | 450 | 451 | ### Arithmetic Field Keywords 452 | 453 | These keywords will increase/decrease the `init` values by the provided steps. 454 | 455 | #### Number Arithmetic 456 | 457 | | Keyword API | Description | 458 | | ------------------------------------------ | --------------------------------------- | 459 | | add(Decimal _init_, Decimal _step_) | Must be applied to a number type field. | 460 | | substract(Decimal _init_, Decimal _step_) | Must be applied to a number type field. | 461 | | divide(Decimal _init_, Decimal _factor_) | Must be applied to a number type field. | 462 | | multiply(Decimal _init_, Decimal _factor_) | Must be applied to a number type field. | 463 | 464 | #### Date/Time Arithmetic 465 | 466 | | Keyword API | Description | 467 | | ----------------------------------------- | ---------------------------------------------- | 468 | | addYears(Object _init_, Integer _step_) | Must be applied to a Datetime/Date type field. | 469 | | addMonths(Object _init_, Integer _step_) | Must be applied to a Datetime/Date type field. | 470 | | addDays(Object _init_, Integer _step_) | Must be applied to a Datetime/Date type field. | 471 | | addHours(Object _init_, Integer _step_) | Must be applied to a Datetime/Time type field. | 472 | | addMinutes(Object _init_, Integer _step_) | Must be applied to a Datetime/Time type field. | 473 | | addSeconds(Object _init_, Integer _step_) | Must be applied to a Datetime/Time type field. | 474 | 475 | ### Lookup Field Keywords 476 | 477 | These are field keywords in nature, but without the need to be chained after `.field()`. ATK will help to look up the IDs and assign them to the correct relationship fields automatically. 478 | 479 | ```java 480 | ATK.prepare(Account.SObjectType, 10) 481 | .recordType('Business_Account'); // case sensitive 482 | 483 | ATK.prepare(User.SObjectType, 10) 484 | .profile('Chatter Free User') // must be applied to User SObject 485 | .permissionSet('Survey_Creator'); // must be applied to User SObject 486 | ``` 487 | 488 | | Keyword API | Description | 489 | | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 490 | | recordType(String _name_) | Assign record type ID by developer name, the name is case sensitive due the `getRecordTypeInfosByDeveloperName()` API. | 491 | | profile(String _name_) | Assign profile ID by profile name. | 492 | | permissionSet(String _name_) | Assign the permission set to users by developer name. | 493 | | permissionSet(String name1, String _name2_) | Assign all the permission sets to users by developer names. | 494 | | permissionSet(String _name1_, String _name2_, String _name3_) | Assign all the permission sets to users by developer names. | 495 | | permissionSet(List\ _names_) | Assign all the permission sets to users by developer names. | 496 | 497 | ## Entity Builder Factory 498 | 499 | In order to increase the reusability of ATK, we can abstract the field keywords into an Entity Builder. 500 | 501 | ```java 502 | @IsTest 503 | public with sharing class CampaignServiceTest { 504 | @TestSetup 505 | static void setup() { 506 | ATK.SaveResult result = ATK.prepare(Campaign.SObjectType, 4) 507 | .build(EntityBuilderFactory.campaignBuilder) // Reference to Entity Builder 508 | .withChildren(CampaignMember.SObjectType, CampaignMember.CampaignId, 8) 509 | .withParents(Lead.SObjectType, CampaignMember.LeadId, 8) 510 | .build(EntityBuilderFactory.leadBuilder) // Reference to Entity Builder 511 | .save(); 512 | } 513 | } 514 | ``` 515 | 516 | ```java 517 | @IsTest 518 | public with sharing class EntityBuilderFactory { 519 | public static CampaignEntityBuilder campaignBuilder = new CampaignEntityBuilder(); 520 | public static LeadEntityBuilder leadBuilder = new LeadEntityBuilder(); 521 | 522 | // Inner class implements ATK.EntityBuilder 523 | public class CampaignEntityBuilder implements ATK.EntityBuilder { 524 | public void build(ATK.Entity campaignEntity, Integer size) { 525 | campaignEntity 526 | .field(Campaign.Type).repeat('Partners') 527 | .field(Campaign.Name).index('Name-{0000}') 528 | .field(Campaign.StartDate).repeat(Date.newInstance(2020, 1, 1)) 529 | .field(Campaign.EndDate).repeat(Date.newInstance(2020, 1, 1)); 530 | } 531 | } 532 | 533 | // Inner class implements ATK.EntityBuilder 534 | public class LeadEntityBuilder implements ATK.EntityBuilder { 535 | public void build(ATK.Entity leadEntity, Integer size) { 536 | leadEntity 537 | .field(Lead.Company).index('Name-{0000}') 538 | .field(Lead.LastName).index('Name-{0000}') 539 | .field(Lead.Email).index('test.user+{0000}@email.com') 540 | .field(Lead.MobilePhone).index('+86 186 7777 {0000}'); 541 | } 542 | } 543 | } 544 | ``` 545 | 546 | ## License 547 | 548 | Apache 2.0 549 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATK.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Jeff Jin 3 | * https://github.com/apexfarm/ApexTestKit 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | public with sharing class ATK { 19 | public virtual class MockException extends Exception { 20 | } 21 | 22 | // ============== 23 | // #region Entity 24 | 25 | public static JunctionEntity prepare(Schema.SObjectType objectType, Integer size) { 26 | ATKCore.EntityCommand sharedCommand = new ATKCore.EntityCommand(); 27 | sharedCommand.prepare(objectType, size); 28 | return sharedCommand; 29 | } 30 | 31 | public static Entity prepare(Schema.SObjectType objectType, List objects) { 32 | ATKCore.EntityCommand sharedCommand = new ATKCore.EntityCommand(); 33 | sharedCommand.prepare(objectType, objects); 34 | return sharedCommand; 35 | } 36 | 37 | public static Id fakeId(Schema.SObjectType objectType) { 38 | return ATKCore.FAKEID.get(objectType); 39 | } 40 | 41 | public static Id fakeId(Schema.SObjectType objectType, Integer index) { 42 | return ATKCore.FAKEID.get(objectType, index); 43 | } 44 | 45 | public interface JunctionEntity extends Entity { 46 | Entity junctionOf(Schema.SObjectField parentIdField1, Schema.SObjectField parentIdField2); 47 | Entity junctionOf(Schema.SObjectField parentIdField1, Schema.SObjectField parentIdField2, Schema.SObjectField parentIdField3); 48 | Entity junctionOf(Schema.SObjectField parentIdField1, Schema.SObjectField parentIdField2, Schema.SObjectField parentIdField3, Schema.SObjectField parentIdField4); 49 | Entity junctionOf( 50 | Schema.SObjectField parentIdField1, 51 | Schema.SObjectField parentIdField2, 52 | Schema.SObjectField parentIdField3, 53 | Schema.SObjectField parentIdField4, 54 | Schema.SObjectField parentIdField5 55 | ); 56 | Entity junctionOf(List parentIdFields); 57 | } 58 | 59 | public interface Entity { 60 | // keywords to start new context 61 | JunctionEntity withParents(Schema.SObjectType objectType, Schema.SObjectField parentIdField); 62 | JunctionEntity withParents(Schema.SObjectType objectType, Schema.SObjectField parentIdField, Integer size); 63 | JunctionEntity withParents(Schema.SObjectType objectType, Schema.SObjectField parentIdField, List objects); 64 | JunctionEntity withChildren(Schema.SObjectType objectType, Schema.SObjectField parentIdField); 65 | JunctionEntity withChildren(Schema.SObjectType objectType, Schema.SObjectField parentIdField, Integer size); 66 | JunctionEntity withChildren(Schema.SObjectType objectType, Schema.SObjectField parentIdField, List objects); 67 | 68 | // keywords to build graph 69 | Entity also(); 70 | Entity also(Integer depth); 71 | Entity build(ATK.EntityBuilder builder); 72 | Field field(SObjectField field); 73 | 74 | // keywords to lookup relation 75 | Entity recordType(String name); 76 | Entity profile(String name); 77 | Entity permissionSet(String name); 78 | Entity permissionSet(String name1, String name2); 79 | Entity permissionSet(String name1, String name2, String name3); 80 | Entity permissionSet(List names); 81 | 82 | // keywords to end with 83 | SaveResult save(); 84 | SaveResult save(Boolean doInsert); 85 | SaveResult mock(); 86 | } 87 | 88 | public interface Field { 89 | // deprecated on 3.2.0 90 | Entity recordType(String name); 91 | Entity profile(String name); 92 | Entity permissionSet(String name); 93 | Entity permissionSet(String name1, String name2); 94 | Entity permissionSet(String name1, String name2, String name3); 95 | Entity permissionSet(List names); 96 | 97 | // Fixed Values 98 | Entity index(String format); 99 | Entity repeat(Object value); 100 | Entity repeat(Object value1, Object value2); 101 | Entity repeat(Object value1, Object value2, Object value3); 102 | Entity repeat(Object value1, Object value2, Object value3, Object value4); 103 | Entity repeat(Object value1, Object value2, Object value3, Object value4, Object value5); 104 | Entity repeat(List values); 105 | Entity repeatX(Object value1, Integer size1, Object value2, Integer size2); 106 | Entity repeatX(Object value1, Integer size1, Object value2, Integer size2, Object value3, Integer size3); 107 | Entity repeatX(Object value1, Integer size1, Object value2, Integer size2, Object value3, Integer size3, Object value4, Integer size4); 108 | Entity repeatX(Object value1, Integer size1, Object value2, Integer size2, Object value3, Integer size3, Object value4, Integer size4, Object value5, Integer size5); 109 | Entity repeatX(List values, List sizes); 110 | 111 | // Arithmetic 112 | Entity add(Decimal init, Decimal step); 113 | Entity substract(Decimal init, Decimal step); 114 | Entity divide(Decimal init, Decimal factor); 115 | Entity multiply(Decimal init, Decimal factor); 116 | 117 | Entity addYears(Object init, Integer step); 118 | Entity addMonths(Object init, Integer step); 119 | Entity addDays(Object init, Integer step); 120 | Entity addHours(Object init, Integer step); 121 | Entity addMinutes(Object init, Integer step); 122 | Entity addSeconds(Object init, Integer step); 123 | } 124 | 125 | public interface EntityBuilder { 126 | void build(Entity entity, Integer size); 127 | } 128 | 129 | public interface SaveResult { 130 | List get(SObjectType objectType); 131 | List get(SObjectType objectType, Integer index); 132 | List getAll(SObjectType objectType); 133 | List getIds(SObjectType objectType); 134 | List getIds(SObjectType objectType, Integer index); 135 | List getAllIds(SObjectType objectType); 136 | } 137 | 138 | // #endregion 139 | // ============== 140 | 141 | // ======================= 142 | // #region Mock Interfaces 143 | 144 | private static Mock MOCK { 145 | get { 146 | if (MOCK == null) { 147 | Type mockType = Type.forName('ATKMock'); 148 | if (mockType != null) { 149 | MOCK = (Mock) mockType.newInstance(); 150 | } else { 151 | throw new MockException('Please install/add ATKMock class.'); 152 | } 153 | } 154 | return MOCK; 155 | } 156 | set; 157 | } 158 | public static final Answer RETURNS_DEFAULTS = MOCK.getReturnsDefaults(); 159 | public static final Answer RETURNS_SELF = MOCK.getReturnsSelf(); 160 | public static final Answer RETURNS_MOCKS = MOCK.getReturnsMocks(); 161 | private static final MockingProgress PROGRESS = MOCK.getProgress(); 162 | private static final MatcherRecorder MATCHER_RECORDER = MOCK.getMatcherRecorder(); 163 | 164 | public static void startStubbing() { 165 | PROGRESS.startStubbing(); 166 | } 167 | 168 | public static void stopStubbing() { 169 | PROGRESS.stopStubbing(); 170 | } 171 | 172 | public static MockSettings withSettings() { 173 | return PROGRESS.newCustomSettings(); 174 | } 175 | 176 | public static GlobalSettings mock() { 177 | return PROGRESS.getGlobalSettings(); 178 | } 179 | 180 | public static Object mock(Type mockType) { 181 | return PROGRESS.createMock(mockType); 182 | } 183 | 184 | public static Object mock(Type mockType, Answer defaultAnswer) { 185 | return PROGRESS.createMock(mockType, defaultAnswer); 186 | } 187 | 188 | public static Object mock(Type mockType, MockSettings settings) { 189 | return PROGRESS.createMock(mockType, settings); 190 | } 191 | 192 | public static Lenient lenient() { 193 | return PROGRESS.createLenientStubber(); 194 | } 195 | 196 | public static Given given(Object value) { 197 | return PROGRESS.createGivenStubber(); 198 | } 199 | 200 | public static Will willReturn(Object value) { 201 | return PROGRESS.createWillStubber().willReturn(value); 202 | } 203 | 204 | public static Will willAnswer(Answer answer) { 205 | return PROGRESS.createWillStubber().willAnswer(answer); 206 | } 207 | 208 | public static Will willThrow(Exception exp) { 209 | return PROGRESS.createWillStubber().willThrow(exp); 210 | } 211 | 212 | public static Will willDoNothing() { 213 | return PROGRESS.createWillStubber().willDoNothing(); 214 | } 215 | 216 | public static ThenOf then(Object mock) { 217 | return PROGRESS.createThenStubber(mock); 218 | } 219 | 220 | public static InOrder inOrder(List mocks) { 221 | return PROGRESS.createInOrder(mocks); 222 | } 223 | 224 | public static InOrder inOrder(Object mock1) { 225 | return inOrder(new List{ mock1 }); 226 | } 227 | 228 | public static InOrder inOrder(Object mock1, Object mock2) { 229 | return inOrder(new List{ mock1, mock2 }); 230 | } 231 | 232 | public static InOrder inOrder(Object mock1, Object mock2, Object mock3) { 233 | return inOrder(new List{ mock1, mock2, mock3 }); 234 | } 235 | 236 | public static InOrder inOrder(Object mock1, Object mock2, Object mock3, Object mock4) { 237 | return inOrder(new List{ mock1, mock2, mock3, mock4 }); 238 | } 239 | 240 | public static InOrder inOrder(Object mock1, Object mock2, Object mock3, Object mock4, Object mock5) { 241 | return inOrder(new List{ mock1, mock2, mock3, mock4, mock5 }); 242 | } 243 | 244 | public interface Mock { 245 | ATK.Answer getReturnsDefaults(); 246 | ATK.Answer getReturnsSelf(); 247 | ATK.Answer getReturnsMocks(); 248 | ATK.MockingProgress getProgress(); 249 | ATK.MatcherRecorder getMatcherRecorder(); 250 | } 251 | 252 | public interface MockingProgress { 253 | void startStubbing(); 254 | void stopStubbing(); 255 | MockSettings newCustomSettings(); 256 | GlobalSettings getGlobalSettings(); 257 | Object createMock(Type mockType); 258 | Object createMock(Type mockType, ATK.Answer defaultAnswer); 259 | Object createMock(Type mockType, ATK.MockSettings settings); 260 | Lenient createLenientStubber(); 261 | Given createGivenStubber(); 262 | Will createWillStubber(); 263 | ThenOf createThenStubber(Object mock); 264 | InOrder createInOrder(List mocks); 265 | } 266 | 267 | public interface MockSettings { 268 | MockSettings name(String name); 269 | MockSettings defaultAnswer(Answer answer); 270 | MockSettings stubOnly(); 271 | MockSettings lenient(); 272 | MockSettings stubbedVoids(); 273 | MockSettings verbose(); 274 | } 275 | 276 | public interface GlobalSettings { 277 | MockSettings withSettings(); 278 | } 279 | 280 | public interface Lenient { 281 | Given given(Object mock); 282 | Will willReturn(Object value); 283 | Will willAnswer(Answer answer); 284 | Will willThrow(Exception exp); 285 | Will willDoNothing(); 286 | } 287 | 288 | public interface Given { 289 | Given willReturn(Object value); 290 | Given willAnswer(Answer answer); 291 | Given willThrow(Exception exp); 292 | } 293 | 294 | public interface Will { 295 | Will willReturn(Object value); 296 | Will willAnswer(Answer answer); 297 | Will willThrow(Exception exp); 298 | Will willDoNothing(); 299 | Object given(Object mock); 300 | } 301 | 302 | public interface Answer { 303 | Object answer(Invocation invocation); 304 | } 305 | 306 | public virtual class Method { 307 | public String name { get; protected set; } 308 | public List paramTypes { get; protected set; } 309 | public List paramNames { get; protected set; } 310 | public Type returnType { get; protected set; } 311 | } 312 | 313 | public virtual class Invocation { 314 | public Method method { get; protected set; } 315 | public Object mock { get; protected set; } 316 | public Type mockType { get; protected set; } 317 | public List arguments { get; protected set; } 318 | } 319 | 320 | public interface ThenOf { 321 | Should should(); 322 | InOrderShould should(InOrder inOrder); 323 | } 324 | 325 | public interface InOrder { 326 | } 327 | 328 | public interface Should { 329 | void haveNoInteractions(); 330 | void haveNoMoreInteractions(); 331 | void haveNoUnusedStubs(); 332 | 333 | Object never(); 334 | Object once(); 335 | Object times(Integer n); 336 | Object atLeast(Integer n); 337 | Object atLeastOnce(); 338 | Object atMost(Integer n); 339 | Object atMostOnce(); 340 | } 341 | 342 | public interface InOrderShould { 343 | void haveNoMoreInteractions(); 344 | 345 | Object never(); 346 | Object once(); 347 | Object times(Integer n); 348 | Object calls(Integer n); 349 | } 350 | 351 | public interface GlobalShould { 352 | void haveNoInteractions(); 353 | void haveNoInteractions(Object mock1); 354 | void haveNoInteractions(Object mock1, Object mock2); 355 | void haveNoInteractions(Object mock1, Object mock2, Object mock3); 356 | void haveNoInteractions(Object mock1, Object mock2, Object mock3, Object mock4); 357 | void haveNoInteractions(Object mock1, Object mock2, Object mock3, Object mock4, Object mock5); 358 | void haveNoInteractions(List mocks); 359 | void haveNoMoreInteractions(); 360 | void haveNoMoreInteractions(Object mock1); 361 | void haveNoMoreInteractions(Object mock1, Object mock2); 362 | void haveNoMoreInteractions(Object mock1, Object mock2, Object mock3); 363 | void haveNoMoreInteractions(Object mock1, Object mock2, Object mock3, Object mock4); 364 | void haveNoMoreInteractions(Object mock1, Object mock2, Object mock3, Object mock4, Object mock5); 365 | void haveNoMoreInteractions(List mocks); 366 | void haveNoUnusedStubs(); 367 | void haveNoUnusedStubs(Object mock1); 368 | void haveNoUnusedStubs(Object mock1, Object mock2); 369 | void haveNoUnusedStubs(Object mock1, Object mock2, Object mock3); 370 | void haveNoUnusedStubs(Object mock1, Object mock2, Object mock3, Object mock4); 371 | void haveNoUnusedStubs(Object mock1, Object mock2, Object mock3, Object mock4, Object mock5); 372 | void haveNoUnusedStubs(List mocks); 373 | } 374 | 375 | public interface Matcher { 376 | Boolean matches(Type type, Object arg); 377 | } 378 | 379 | public interface MatcherRecorder { 380 | // Type Matchers 381 | Integer anyInteger(); 382 | Long anyLong(); 383 | Double anyDouble(); 384 | Decimal anyDecimal(); 385 | Date anyDate(); 386 | Datetime anyDatetime(); 387 | Time anyTime(); 388 | Id anyId(); 389 | String anyString(); 390 | Boolean anyBoolean(); 391 | 392 | List anyList(); 393 | Object anySet(); 394 | Object anyMap(); 395 | 396 | Object any(); 397 | Object any(Type type); 398 | Object nullable(Type type); 399 | 400 | SObject anySObject(); 401 | List anySObjectList(); 402 | 403 | // Value Matchers 404 | Object isNull(); 405 | Object isNotNull(); 406 | Object same(Object value); 407 | 408 | Object ne(Object value); 409 | Integer neInteger(Integer value); 410 | Long neLong(Long value); 411 | Double neDouble(Double value); 412 | Decimal neDecimal(Decimal value); 413 | Date neDate(Date value); 414 | Datetime neDatetime(Datetime value); 415 | Time neTime(Time value); 416 | Id neId(Id value); 417 | String neString(String value); 418 | Boolean neBoolean(Boolean value); 419 | 420 | Object eq(Object value); 421 | Integer eqInteger(Integer value); 422 | Long eqLong(Long value); 423 | Double eqDouble(Double value); 424 | Decimal eqDecimal(Decimal value); 425 | Date eqDate(Date value); 426 | Datetime eqDatetime(Datetime value); 427 | Time eqTime(Time value); 428 | Id eqId(Id value); 429 | String eqString(String value); 430 | Boolean eqBoolean(Boolean value); 431 | 432 | Integer gt(Integer value); 433 | Long gt(Long value); 434 | Double gt(Double value); 435 | Decimal gt(Decimal value); 436 | Date gt(Date value); 437 | Datetime gt(Datetime value); 438 | Time gt(Time value); 439 | Id gt(Id value); 440 | String gt(String value); 441 | 442 | Integer gte(Integer value); 443 | Long gte(Long value); 444 | Double gte(Double value); 445 | Decimal gte(Decimal value); 446 | Date gte(Date value); 447 | Datetime gte(Datetime value); 448 | Time gte(Time value); 449 | Id gte(Id value); 450 | String gte(String value); 451 | 452 | Integer lt(Integer value); 453 | Long lt(Long value); 454 | Double lt(Double value); 455 | Decimal lt(Decimal value); 456 | Date lt(Date value); 457 | Datetime lt(Datetime value); 458 | Time lt(Time value); 459 | Id lt(Id value); 460 | String lt(String value); 461 | 462 | Integer lte(Integer value); 463 | Long lte(Long value); 464 | Double lte(Double value); 465 | Decimal lte(Decimal value); 466 | Date lte(Date value); 467 | Datetime lte(Datetime value); 468 | Time lte(Time value); 469 | Id lte(Id value); 470 | String lte(String value); 471 | 472 | Integer between(Integer min, Boolean minInclusive, Integer max, Boolean maxInclusive); 473 | Long between(Long min, Boolean minInclusive, Long max, Boolean maxInclusive); 474 | Double between(Double min, Boolean minInclusive, Double max, Boolean maxInclusive); 475 | Decimal between(Decimal min, Boolean minInclusive, Decimal max, Boolean maxInclusive); 476 | Date between(Date min, Boolean minInclusive, Date max, Boolean maxInclusive); 477 | Datetime between(Datetime min, Boolean minInclusive, Datetime max, Boolean maxInclusive); 478 | Time between(Time min, Boolean minInclusive, Time max, Boolean maxInclusive); 479 | Id between(Id min, Boolean minInclusive, Id max, Boolean maxInclusive); 480 | String between(String min, Boolean minInclusive, String max, Boolean maxInclusive); 481 | 482 | String isBlank(); 483 | String isNotBlank(); 484 | String startsWith(String value); 485 | String endsWith(String value); 486 | String matches(String regexp); 487 | String contains(String value); 488 | 489 | // TODO: implement list matchers 490 | // Object listContains(Object value); 491 | // Object listIsEmpty(); 492 | 493 | SObject sObjectWithId(Id value); 494 | SObject sObjectWithName(String value); 495 | SObject sObjectWith(SObjectField field, Object value); 496 | SObject sObjectWith(Map value); 497 | SObject sObjectWith(Map value); 498 | LIst sObjectListWith(SObjectField field, Object value); 499 | LIst sObjectListWith(Map value); 500 | LIst sObjectListWith(List> value, Boolean inOrder); 501 | 502 | // Combo Matchers 503 | Object allOf(Object arg1, Object arg2); 504 | Object allOf(Object arg1, Object arg2, Object arg3); 505 | Object allOf(Object arg1, Object arg2, Object arg3, Object arg4); 506 | Object allOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5); 507 | Object allOf(List args); 508 | Object anyOf(Object arg1, Object arg2); 509 | Object anyOf(Object arg1, Object arg2, Object arg3); 510 | Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4); 511 | Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5); 512 | Object anyOf(List args); 513 | Object isNot(Object arg1); 514 | Object noneOf(Object arg1, Object arg2); 515 | Object noneOf(Object arg1, Object arg2, Object arg3); 516 | Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4); 517 | Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5); 518 | Object noneOf(List args); 519 | } 520 | 521 | // #endregion 522 | // ======================= 523 | 524 | // ========================= 525 | // #region Argument Matchers 526 | 527 | // #region - Type Matchers 528 | public static Integer anyInteger() { 529 | return MATCHER_RECORDER.anyInteger(); 530 | } 531 | 532 | public static Long anyLong() { 533 | return MATCHER_RECORDER.anyLong(); 534 | } 535 | 536 | public static Double anyDouble() { 537 | return MATCHER_RECORDER.anyDouble(); 538 | } 539 | 540 | public static Decimal anyDecimal() { 541 | return MATCHER_RECORDER.anyDecimal(); 542 | } 543 | 544 | public static Date anyDate() { 545 | return MATCHER_RECORDER.anyDate(); 546 | } 547 | 548 | public static Datetime anyDatetime() { 549 | return MATCHER_RECORDER.anyDatetime(); 550 | } 551 | 552 | public static Time anyTime() { 553 | return MATCHER_RECORDER.anyTime(); 554 | } 555 | 556 | public static Id anyId() { 557 | return MATCHER_RECORDER.anyId(); 558 | } 559 | 560 | public static String anyString() { 561 | return MATCHER_RECORDER.anyString(); 562 | } 563 | 564 | public static Boolean anyBoolean() { 565 | return MATCHER_RECORDER.anyBoolean(); 566 | } 567 | 568 | public static List anyList() { 569 | return MATCHER_RECORDER.anyList(); 570 | } 571 | 572 | public static Object anySet() { 573 | return MATCHER_RECORDER.anySet(); 574 | } 575 | 576 | public static Object anyMap() { 577 | return MATCHER_RECORDER.anyMap(); 578 | } 579 | 580 | public static Object any() { 581 | return MATCHER_RECORDER.any(); 582 | } 583 | 584 | public static Object any(Type type) { 585 | return MATCHER_RECORDER.any(type); 586 | } 587 | 588 | public static Object nullable(Type type) { 589 | return MATCHER_RECORDER.nullable(type); 590 | } 591 | 592 | public static SObject anySObject() { 593 | return MATCHER_RECORDER.anySObject(); 594 | } 595 | 596 | public static List anySObjectList() { 597 | return MATCHER_RECORDER.anySObjectList(); 598 | } 599 | // #endregion 600 | 601 | // #region - Value Matchers 602 | public static Object isNull() { 603 | return MATCHER_RECORDER.isNull(); 604 | } 605 | 606 | public static Object isNotNull() { 607 | return MATCHER_RECORDER.isNotNull(); 608 | } 609 | 610 | public static Object same(Object value) { 611 | return MATCHER_RECORDER.same(value); 612 | } 613 | 614 | public static Object ne(Object value) { 615 | return MATCHER_RECORDER.ne(value); 616 | } 617 | 618 | public static Integer neInteger(Integer value) { 619 | return MATCHER_RECORDER.neInteger(value); 620 | } 621 | 622 | public static Long neLong(Long value) { 623 | return MATCHER_RECORDER.neLong(value); 624 | } 625 | 626 | public static Double neDouble(Double value) { 627 | return MATCHER_RECORDER.neDouble(value); 628 | } 629 | 630 | public static Decimal neDecimal(Decimal value) { 631 | return MATCHER_RECORDER.neDecimal(value); 632 | } 633 | 634 | public static Date neDate(Date value) { 635 | return MATCHER_RECORDER.neDate(value); 636 | } 637 | 638 | public static Datetime neDatetime(Datetime value) { 639 | return MATCHER_RECORDER.neDatetime(value); 640 | } 641 | 642 | public static Time neTime(Time value) { 643 | return MATCHER_RECORDER.neTime(value); 644 | } 645 | 646 | public static Id neId(Id value) { 647 | return MATCHER_RECORDER.neId(value); 648 | } 649 | 650 | public static String neString(String value) { 651 | return MATCHER_RECORDER.neString(value); 652 | } 653 | 654 | public static Boolean neBoolean(Boolean value) { 655 | return MATCHER_RECORDER.neBoolean(value); 656 | } 657 | 658 | public static Object eq(Object value) { 659 | return MATCHER_RECORDER.eq(value); 660 | } 661 | 662 | public static Integer eqInteger(Integer value) { 663 | return MATCHER_RECORDER.eqInteger(value); 664 | } 665 | 666 | public static Long eqLong(Long value) { 667 | return MATCHER_RECORDER.eqLong(value); 668 | } 669 | 670 | public static Double eqDouble(Double value) { 671 | return MATCHER_RECORDER.eqDouble(value); 672 | } 673 | 674 | public static Decimal eqDecimal(Decimal value) { 675 | return MATCHER_RECORDER.eqDecimal(value); 676 | } 677 | 678 | public static Date eqDate(Date value) { 679 | return MATCHER_RECORDER.eqDate(value); 680 | } 681 | 682 | public static Datetime eqDatetime(Datetime value) { 683 | return MATCHER_RECORDER.eqDatetime(value); 684 | } 685 | 686 | public static Time eqTime(Time value) { 687 | return MATCHER_RECORDER.eqTime(value); 688 | } 689 | 690 | public static Id eqId(Id value) { 691 | return MATCHER_RECORDER.eqId(value); 692 | } 693 | 694 | public static String eqString(String value) { 695 | return MATCHER_RECORDER.eqString(value); 696 | } 697 | 698 | public static Boolean eqBoolean(Boolean value) { 699 | return MATCHER_RECORDER.eqBoolean(value); 700 | } 701 | 702 | public static Integer gt(Integer value) { 703 | return MATCHER_RECORDER.gt(value); 704 | } 705 | 706 | public static Long gt(Long value) { 707 | return MATCHER_RECORDER.gt(value); 708 | } 709 | 710 | public static Double gt(Double value) { 711 | return MATCHER_RECORDER.gt(value); 712 | } 713 | 714 | public static Decimal gt(Decimal value) { 715 | return MATCHER_RECORDER.gt(value); 716 | } 717 | 718 | public static Date gt(Date value) { 719 | return MATCHER_RECORDER.gt(value); 720 | } 721 | 722 | public static Datetime gt(Datetime value) { 723 | return MATCHER_RECORDER.gt(value); 724 | } 725 | 726 | public static Time gt(Time value) { 727 | return MATCHER_RECORDER.gt(value); 728 | } 729 | 730 | public static Id gt(Id value) { 731 | return MATCHER_RECORDER.gt(value); 732 | } 733 | 734 | public static String gt(String value) { 735 | return MATCHER_RECORDER.gt(value); 736 | } 737 | 738 | public static Integer gte(Integer value) { 739 | return MATCHER_RECORDER.gte(value); 740 | } 741 | 742 | public static Long gte(Long value) { 743 | return MATCHER_RECORDER.gte(value); 744 | } 745 | 746 | public static Double gte(Double value) { 747 | return MATCHER_RECORDER.gte(value); 748 | } 749 | 750 | public static Decimal gte(Decimal value) { 751 | return MATCHER_RECORDER.gte(value); 752 | } 753 | 754 | public static Date gte(Date value) { 755 | return MATCHER_RECORDER.gte(value); 756 | } 757 | 758 | public static Datetime gte(Datetime value) { 759 | return MATCHER_RECORDER.gte(value); 760 | } 761 | 762 | public static Time gte(Time value) { 763 | return MATCHER_RECORDER.gte(value); 764 | } 765 | 766 | public static Id gte(Id value) { 767 | return MATCHER_RECORDER.gte(value); 768 | } 769 | 770 | public static String gte(String value) { 771 | return MATCHER_RECORDER.gte(value); 772 | } 773 | 774 | public static Integer lt(Integer value) { 775 | return MATCHER_RECORDER.lt(value); 776 | } 777 | 778 | public static Long lt(Long value) { 779 | return MATCHER_RECORDER.lt(value); 780 | } 781 | 782 | public static Double lt(Double value) { 783 | return MATCHER_RECORDER.lt(value); 784 | } 785 | 786 | public static Decimal lt(Decimal value) { 787 | return MATCHER_RECORDER.lt(value); 788 | } 789 | 790 | public static Date lt(Date value) { 791 | return MATCHER_RECORDER.lt(value); 792 | } 793 | 794 | public static Datetime lt(Datetime value) { 795 | return MATCHER_RECORDER.lt(value); 796 | } 797 | 798 | public static Time lt(Time value) { 799 | return MATCHER_RECORDER.lt(value); 800 | } 801 | 802 | public static Id lt(Id value) { 803 | return MATCHER_RECORDER.lt(value); 804 | } 805 | 806 | public static String lt(String value) { 807 | return MATCHER_RECORDER.lt(value); 808 | } 809 | 810 | public static Integer lte(Integer value) { 811 | return MATCHER_RECORDER.lte(value); 812 | } 813 | 814 | public static Long lte(Long value) { 815 | return MATCHER_RECORDER.lte(value); 816 | } 817 | 818 | public static Double lte(Double value) { 819 | return MATCHER_RECORDER.lte(value); 820 | } 821 | 822 | public static Decimal lte(Decimal value) { 823 | return MATCHER_RECORDER.lte(value); 824 | } 825 | 826 | public static Date lte(Date value) { 827 | return MATCHER_RECORDER.lte(value); 828 | } 829 | 830 | public static Datetime lte(Datetime value) { 831 | return MATCHER_RECORDER.lte(value); 832 | } 833 | 834 | public static Time lte(Time value) { 835 | return MATCHER_RECORDER.lte(value); 836 | } 837 | 838 | public static Id lte(Id value) { 839 | return MATCHER_RECORDER.lte(value); 840 | } 841 | 842 | public static String lte(String value) { 843 | return MATCHER_RECORDER.lte(value); 844 | } 845 | 846 | public static Integer between(Integer min, Integer max) { 847 | return MATCHER_RECORDER.between(min, true, max, true); 848 | } 849 | 850 | public static Integer between(Integer min, Integer max, Boolean inclusive) { 851 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 852 | } 853 | 854 | public static Integer between(Integer min, Boolean minInclusive, Integer max, Boolean maxInclusive) { 855 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 856 | } 857 | 858 | public static Long between(Long min, Long max) { 859 | return MATCHER_RECORDER.between(min, true, max, true); 860 | } 861 | 862 | public static Long between(Long min, Long max, Boolean inclusive) { 863 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 864 | } 865 | 866 | public static Long between(Long min, Boolean minInclusive, Long max, Boolean maxInclusive) { 867 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 868 | } 869 | 870 | public static Double between(Double min, Double max) { 871 | return MATCHER_RECORDER.between(min, true, max, true); 872 | } 873 | 874 | public static Double between(Double min, Double max, Boolean inclusive) { 875 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 876 | } 877 | 878 | public static Double between(Double min, Boolean minInclusive, Double max, Boolean maxInclusive) { 879 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 880 | } 881 | 882 | public static Decimal between(Decimal min, Decimal max) { 883 | return MATCHER_RECORDER.between(min, true, max, true); 884 | } 885 | 886 | public static Decimal between(Decimal min, Decimal max, Boolean inclusive) { 887 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 888 | } 889 | 890 | public static Decimal between(Decimal min, Boolean minInclusive, Decimal max, Boolean maxInclusive) { 891 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 892 | } 893 | 894 | public static Date between(Date min, Date max) { 895 | return MATCHER_RECORDER.between(min, true, max, true); 896 | } 897 | 898 | public static Date between(Date min, Date max, Boolean inclusive) { 899 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 900 | } 901 | 902 | public static Date between(Date min, Boolean minInclusive, Date max, Boolean maxInclusive) { 903 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 904 | } 905 | 906 | public static Datetime between(Datetime min, Datetime max) { 907 | return MATCHER_RECORDER.between(min, true, max, true); 908 | } 909 | 910 | public static Datetime between(Datetime min, Datetime max, Boolean inclusive) { 911 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 912 | } 913 | 914 | public static Datetime between(Datetime min, Boolean minInclusive, Datetime max, Boolean maxInclusive) { 915 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 916 | } 917 | 918 | public static Time between(Time min, Time max) { 919 | return MATCHER_RECORDER.between(min, true, max, true); 920 | } 921 | 922 | public static Time between(Time min, Time max, Boolean inclusive) { 923 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 924 | } 925 | 926 | public static Time between(Time min, Boolean minInclusive, Time max, Boolean maxInclusive) { 927 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 928 | } 929 | 930 | public static Id between(Id min, Id max) { 931 | return MATCHER_RECORDER.between(min, true, max, true); 932 | } 933 | 934 | public static Id between(Id min, Id max, Boolean inclusive) { 935 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 936 | } 937 | 938 | public static Id between(Id min, Boolean minInclusive, Id max, Boolean maxInclusive) { 939 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 940 | } 941 | 942 | public static String between(String min, String max) { 943 | return MATCHER_RECORDER.between(min, true, max, true); 944 | } 945 | 946 | public static String between(String min, String max, Boolean inclusive) { 947 | return MATCHER_RECORDER.between(min, inclusive, max, inclusive); 948 | } 949 | 950 | public static String between(String min, Boolean minInclusive, String max, Boolean maxInclusive) { 951 | return MATCHER_RECORDER.between(min, minInclusive, max, maxInclusive); 952 | } 953 | 954 | public static String isBlank() { 955 | return MATCHER_RECORDER.isBlank(); 956 | } 957 | 958 | public static String isNotBlank() { 959 | return MATCHER_RECORDER.isNotBlank(); 960 | } 961 | 962 | public static String contains(String value) { 963 | return MATCHER_RECORDER.contains(value); 964 | } 965 | 966 | public static String startsWith(String value) { 967 | return MATCHER_RECORDER.startsWith(value); 968 | } 969 | 970 | public static String endsWith(String value) { 971 | return MATCHER_RECORDER.endsWith(value); 972 | } 973 | 974 | public static String matches(String regexp) { 975 | return MATCHER_RECORDER.matches(regexp); 976 | } 977 | 978 | public static SObject sObjectWithId(Id value) { 979 | return MATCHER_RECORDER.sObjectWithId(value); 980 | } 981 | 982 | public static SObject sObjectWithName(String value) { 983 | return MATCHER_RECORDER.sObjectWithName(value); 984 | } 985 | 986 | public static SObject sObjectWith(SObjectField field, Object value) { 987 | return MATCHER_RECORDER.sObjectWith(field, value); 988 | } 989 | 990 | public static SObject sObjectWith(Map value) { 991 | return MATCHER_RECORDER.sObjectWith(value); 992 | } 993 | 994 | public static LIst sObjectListWith(SObjectField field, Object value) { 995 | return MATCHER_RECORDER.sObjectListWith(field, value); 996 | } 997 | 998 | public static LIst sObjectListWith(Map value) { 999 | return MATCHER_RECORDER.sObjectListWith(value); 1000 | } 1001 | 1002 | public static LIst sObjectListWith(List> value) { 1003 | return MATCHER_RECORDER.sObjectListWith(value, false); 1004 | } 1005 | 1006 | public static LIst sObjectListWith(List> value, Boolean inOrder) { 1007 | return MATCHER_RECORDER.sObjectListWith(value, inOrder); 1008 | } 1009 | // #endregion 1010 | 1011 | // #region - Combo Matchers 1012 | public static Object allOf(Object arg1, Object arg2) { 1013 | return MATCHER_RECORDER.allOf(arg1, arg2); 1014 | } 1015 | 1016 | public static Object allOf(Object arg1, Object arg2, Object arg3) { 1017 | return MATCHER_RECORDER.allOf(arg1, arg2, arg3); 1018 | } 1019 | 1020 | public static Object allOf(Object arg1, Object arg2, Object arg3, Object arg4) { 1021 | return MATCHER_RECORDER.allOf(arg1, arg2, arg3, arg4); 1022 | } 1023 | 1024 | public static Object allOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { 1025 | return MATCHER_RECORDER.allOf(arg1, arg2, arg3, arg4, arg5); 1026 | } 1027 | 1028 | public static Object allOf(List args) { 1029 | return MATCHER_RECORDER.allOf(args); 1030 | } 1031 | 1032 | public static Object anyOf(Object arg1, Object arg2) { 1033 | return MATCHER_RECORDER.anyOf(arg1, arg2); 1034 | } 1035 | 1036 | public static Object anyOf(Object arg1, Object arg2, Object arg3) { 1037 | return MATCHER_RECORDER.anyOf(arg1, arg2, arg3); 1038 | } 1039 | 1040 | public static Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4) { 1041 | return MATCHER_RECORDER.anyOf(arg1, arg2, arg3, arg4); 1042 | } 1043 | 1044 | public static Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { 1045 | return MATCHER_RECORDER.anyOf(arg1, arg2, arg3, arg4, arg5); 1046 | } 1047 | 1048 | public static Object anyOf(List args) { 1049 | return MATCHER_RECORDER.anyOf(args); 1050 | } 1051 | 1052 | public static Object isNot(Object arg1) { 1053 | return MATCHER_RECORDER.isNot(arg1); 1054 | } 1055 | 1056 | public static Object noneOf(Object arg1, Object arg2) { 1057 | return MATCHER_RECORDER.noneOf(arg1, arg2); 1058 | } 1059 | 1060 | public static Object noneOf(Object arg1, Object arg2, Object arg3) { 1061 | return MATCHER_RECORDER.noneOf(arg1, arg2, arg3); 1062 | } 1063 | 1064 | public static Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4) { 1065 | return MATCHER_RECORDER.noneOf(arg1, arg2, arg3, arg4); 1066 | } 1067 | 1068 | public static Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { 1069 | return MATCHER_RECORDER.noneOf(arg1, arg2, arg3, arg4, arg5); 1070 | } 1071 | 1072 | public static Object noneOf(List args) { 1073 | return MATCHER_RECORDER.noneOf(args); 1074 | } 1075 | // #endregion 1076 | 1077 | // #endregion 1078 | // ========================= 1079 | 1080 | } 1081 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATK.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKCore.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKCoreTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKMock.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKMockTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKTest.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Jeff Jin 3 | * https://github.com/apexfarm/ApexTestKit 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | @IsTest 19 | public with sharing class ATKTest { 20 | // **************** 21 | // #region Junction 22 | // **************** 23 | 24 | @IsTest 25 | static void test_Junction_LCA() { 26 | // Account(10) --|------------------|--> Case(40) 27 | // |--> Contact(20) --| 28 | // prettier-ignore 29 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 30 | .withChildren(Case.SObjectType, Case.AccountId, 40) 31 | .junctionOf(Case.AccountId, Case.ContactId) 32 | .also() 33 | .withChildren(Contact.SObjectType, Contact.AccountId, 20) 34 | .withChildren(Case.SObjectType, Case.ContactId) 35 | .mock(); 36 | } 37 | 38 | @IsTest 39 | static void test_Junction_Size_Prepare() { 40 | // prettier-ignore 41 | ATK.SaveResult result = ATK.prepare(Case.SObjectType, 20) 42 | .junctionOf(Case.AccountId, Case.ContactId) 43 | .withParents(Account.SObjectType, Case.AccountId, 10) 44 | .also() 45 | .withParents(Contact.SObjectType, Case.ContactId, 10) 46 | .save(false); 47 | ((ATKCore.SaveResult) result).debug(); 48 | ((ATKCore.SaveResult) result).debug(Account.SObjectType); 49 | 50 | List accounts = result.get(Account.SObjectType); 51 | List cases = result.get(Case.SObjectType); 52 | List contacts = result.get(Contact.SObjectType); 53 | 54 | System.assertEquals(10, accounts.size()); 55 | System.assertEquals(20, cases.size()); 56 | System.assertEquals(10, contacts.size()); 57 | 58 | System.assertEquals(accounts[0], cases[0].Account); 59 | System.assertEquals(contacts[0], cases[0].Contact); 60 | System.assertEquals(accounts[0], cases[1].Account); 61 | System.assertEquals(contacts[1], cases[1].Contact); 62 | System.assertEquals(accounts[1], cases[2].Account); 63 | System.assertEquals(contacts[2], cases[2].Contact); 64 | System.assertEquals(accounts[1], cases[3].Account); 65 | System.assertEquals(contacts[3], cases[3].Contact); 66 | 67 | System.assertEquals(accounts[5], cases[10].Account); 68 | System.assertEquals(contacts[0], cases[11].Contact); 69 | System.assertEquals(accounts[5], cases[12].Account); 70 | System.assertEquals(contacts[1], cases[13].Contact); 71 | System.assertEquals(accounts[6], cases[14].Account); 72 | System.assertEquals(contacts[2], cases[15].Contact); 73 | System.assertEquals(accounts[6], cases[16].Account); 74 | System.assertEquals(contacts[3], cases[17].Contact); 75 | } 76 | 77 | @IsTest 78 | static void test_Junction_Size_Children() { 79 | // prettier-ignore 80 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 81 | .withChildren(Case.SObjectType, Case.AccountId, 20) 82 | .junctionOf(Case.AccountId, Case.ContactId) 83 | .withParents(Contact.SObjectType, Case.ContactId, 10) 84 | .save(false); 85 | ((ATKCore.SaveResult) result).debug(); 86 | ((ATKCore.SaveResult) result).debug(Account.SObjectType); 87 | 88 | List accounts = result.get(Account.SObjectType); 89 | List cases = result.get(Case.SObjectType); 90 | List contacts = result.get(Contact.SObjectType); 91 | 92 | System.assertEquals(10, accounts.size()); 93 | System.assertEquals(20, cases.size()); 94 | System.assertEquals(10, contacts.size()); 95 | 96 | System.assertEquals(accounts[0], cases[0].Account); 97 | System.assertEquals(contacts[0], cases[0].Contact); 98 | System.assertEquals(accounts[0], cases[1].Account); 99 | System.assertEquals(contacts[1], cases[1].Contact); 100 | System.assertEquals(accounts[1], cases[2].Account); 101 | System.assertEquals(contacts[2], cases[2].Contact); 102 | System.assertEquals(accounts[1], cases[3].Account); 103 | System.assertEquals(contacts[3], cases[3].Contact); 104 | 105 | System.assertEquals(accounts[5], cases[10].Account); 106 | System.assertEquals(contacts[0], cases[11].Contact); 107 | System.assertEquals(accounts[5], cases[12].Account); 108 | System.assertEquals(contacts[1], cases[13].Contact); 109 | System.assertEquals(accounts[6], cases[14].Account); 110 | System.assertEquals(contacts[2], cases[15].Contact); 111 | System.assertEquals(accounts[6], cases[16].Account); 112 | System.assertEquals(contacts[3], cases[17].Contact); 113 | } 114 | 115 | @IsTest 116 | static void test_Junction_Size_Children2() { 117 | // prettier-ignore 118 | ATK.SaveResult result = ATK.prepare(Contact.SObjectType, 10) 119 | .withChildren(Case.SObjectType, Case.ContactId, 20) 120 | .junctionOf(Case.ContactId, Case.AccountId) 121 | .withParents(Account.SObjectType, Case.AccountId, 10) 122 | .save(false); 123 | List accounts = result.get(Account.SObjectType); 124 | List cases = result.get(Case.SObjectType); 125 | List contacts = result.get(Contact.SObjectType); 126 | 127 | System.assertEquals(10, accounts.size()); 128 | System.assertEquals(20, cases.size()); 129 | System.assertEquals(10, contacts.size()); 130 | 131 | System.assertEquals(contacts[0], cases[0].Contact); 132 | System.assertEquals(accounts[0], cases[0].Account); 133 | System.assertEquals(contacts[0], cases[1].Contact); 134 | System.assertEquals(accounts[1], cases[1].Account); 135 | System.assertEquals(contacts[1], cases[2].Contact); 136 | System.assertEquals(accounts[2], cases[2].Account); 137 | System.assertEquals(contacts[1], cases[3].Contact); 138 | System.assertEquals(accounts[3], cases[3].Account); 139 | 140 | System.assertEquals(contacts[5], cases[10].Contact); 141 | System.assertEquals(accounts[0], cases[11].Account); 142 | System.assertEquals(contacts[5], cases[12].Contact); 143 | System.assertEquals(accounts[1], cases[13].Account); 144 | System.assertEquals(contacts[6], cases[14].Contact); 145 | System.assertEquals(accounts[2], cases[15].Account); 146 | System.assertEquals(contacts[6], cases[16].Contact); 147 | System.assertEquals(accounts[3], cases[17].Account); 148 | } 149 | 150 | @IsTest 151 | static void test_Junction_Size_Zero() { 152 | // prettier-ignore 153 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 0) 154 | .withChildren(Case.SObjectType, Case.AccountId, 0) 155 | .junctionOf(Case.AccountId, Case.ContactId) 156 | .withParents(Contact.SObjectType, Case.ContactId, 0) 157 | .save(false); 158 | List accounts = result.get(Account.SObjectType); 159 | List cases = result.get(Case.SObjectType); 160 | List contacts = result.get(Contact.SObjectType); 161 | 162 | System.assertEquals(0, accounts.size()); 163 | System.assertEquals(0, cases.size()); 164 | System.assertEquals(0, contacts.size()); 165 | } 166 | 167 | @IsTest 168 | static void test_Junction_List() { 169 | List accounts = new List(); 170 | List cases = new List(); 171 | List contacts = new List(); 172 | 173 | for (Integer i = 0; i < 10; ++i) { 174 | accounts.add(new Account()); 175 | cases.add(new Case()); 176 | cases.add(new Case()); 177 | contacts.add(new Contact()); 178 | } 179 | 180 | // prettier-ignore 181 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, accounts) 182 | .withChildren(Case.SObjectType, Case.AccountId, cases) 183 | .junctionOf(Case.AccountId, Case.ContactId) 184 | .withParents(Contact.SObjectType, Case.ContactId, contacts) 185 | .save(false); 186 | System.assertEquals(10, accounts.size()); 187 | System.assertEquals(20, cases.size()); 188 | System.assertEquals(10, contacts.size()); 189 | 190 | System.assertEquals(accounts[0], cases[0].Account); 191 | System.assertEquals(contacts[0], cases[0].Contact); 192 | System.assertEquals(accounts[0], cases[1].Account); 193 | System.assertEquals(contacts[1], cases[1].Contact); 194 | System.assertEquals(accounts[1], cases[2].Account); 195 | System.assertEquals(contacts[2], cases[2].Contact); 196 | System.assertEquals(accounts[1], cases[3].Account); 197 | System.assertEquals(contacts[3], cases[3].Contact); 198 | 199 | System.assertEquals(accounts[6], cases[0].Account); 200 | System.assertEquals(contacts[0], cases[0].Contact); 201 | System.assertEquals(accounts[6], cases[1].Account); 202 | System.assertEquals(contacts[1], cases[1].Contact); 203 | System.assertEquals(accounts[7], cases[2].Account); 204 | System.assertEquals(contacts[2], cases[2].Contact); 205 | System.assertEquals(accounts[7], cases[3].Account); 206 | System.assertEquals(contacts[3], cases[3].Contact); 207 | } 208 | 209 | @IsTest 210 | static void test_Junction_Partial() { 211 | // prettier-ignore 212 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 213 | .withChildren(Case.SObjectType, Case.AccountId, 20) 214 | .save(false); 215 | List accounts = result.get(Account.SObjectType); 216 | List cases = result.get(Case.SObjectType); 217 | 218 | System.assertEquals(10, accounts.size()); 219 | System.assertEquals(20, cases.size()); 220 | System.assertEquals(accounts[0], cases[0].Account); 221 | System.assertEquals(accounts[0], cases[1].Account); 222 | System.assertEquals(accounts[1], cases[2].Account); 223 | System.assertEquals(accounts[1], cases[3].Account); 224 | } 225 | 226 | @IsTest 227 | static void test_Junction_Tripple() { 228 | // TODO: need assersions 229 | { 230 | // prettier-ignore 231 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 232 | .withChildren(Case.SObjectType, Case.AccountId, 20) 233 | .junctionOf(Case.AccountId, Case.ContactId, Case.OwnerId) 234 | .withParents(Contact.SObjectType, Case.ContactId, 10) 235 | .also() 236 | .withParents(User.SObjectType, Case.OwnerId, 10) 237 | .mock(); 238 | List accounts = result.get(Account.SObjectType); 239 | List cases = result.get(Case.SObjectType); 240 | List contacts = result.get(Contact.SObjectType); 241 | List users = result.get(User.SObjectType); 242 | } 243 | 244 | { 245 | // prettier-ignore 246 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 247 | .withChildren(Case.SObjectType, Case.AccountId, 20) 248 | .junctionOf(Case.AccountId, Case.ContactId, Case.OwnerId) 249 | .withParents(Contact.SObjectType, Case.ContactId, 10) 250 | .also() 251 | .withParents(User.SObjectType, Case.OwnerId, 10) 252 | .save(false); 253 | 254 | List accounts = result.get(Account.SObjectType); 255 | List cases = result.get(Case.SObjectType); 256 | List contacts = result.get(Contact.SObjectType); 257 | List users = result.get(User.SObjectType); 258 | } 259 | } 260 | 261 | // #endregion 262 | // ******************** 263 | 264 | // ******************* 265 | // #region One To Many 266 | // ******************* 267 | @IsTest 268 | static void test_OneToMany_Save() { 269 | // Account(10) --|------------------|--> Case(40) 270 | // |--> Contact(20) --| 271 | // prettier-ignore 272 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 273 | .withChildren(Case.SObjectType, Case.AccountId, 40) 274 | .also() 275 | .withChildren(Contact.SObjectType, Contact.AccountId, 20) 276 | .withChildren(Case.SObjectType, Case.ContactId) 277 | .save(false); 278 | for (Integer i = 0; i < 10; i++) { 279 | Account acc = (Account) result.get(Account.SObjectType)[i]; 280 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 281 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 282 | Case case1 = (Case) result.get(Case.SObjectType)[i * 4]; 283 | Case case2 = (Case) result.get(Case.SObjectType)[i * 4 + 1]; 284 | Case case3 = (Case) result.get(Case.SObjectType)[i * 4 + 2]; 285 | Case case4 = (Case) result.get(Case.SObjectType)[i * 4 + 3]; 286 | 287 | System.assertEquals(acc, contact1.Account); 288 | System.assertEquals(acc, contact2.Account); 289 | System.assertEquals(acc, case1.Account); 290 | System.assertEquals(acc, case2.Account); 291 | System.assertEquals(acc, case3.Account); 292 | System.assertEquals(acc, case4.Account); 293 | System.assertEquals(contact1, case1.Contact); 294 | System.assertEquals(contact1, case2.Contact); 295 | System.assertEquals(contact2, case3.Contact); 296 | System.assertEquals(contact2, case4.Contact); 297 | } 298 | } 299 | 300 | @IsTest 301 | static void test_OneToMany_Size() { 302 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10).withChildren(Contact.SObjectType, Contact.AccountId, 20).save(false); 303 | 304 | for (Integer i = 0; i < 10; i++) { 305 | Account acc = (Account) result.get(Account.SObjectType)[i]; 306 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 307 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 308 | 309 | System.assertEquals(acc, contact1.Account); 310 | System.assertEquals(acc, contact2.Account); 311 | } 312 | } 313 | 314 | @IsTest 315 | static void test_OneToMany_Size_Zero() { 316 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 0).withChildren(Contact.SObjectType, Contact.AccountId, 0).save(false); 317 | 318 | List accounts = result.get(Account.SObjectType); 319 | List contacts = result.get(Contact.SObjectType); 320 | 321 | System.assertEquals(0, accounts.size()); 322 | System.assertEquals(0, contacts.size()); 323 | } 324 | 325 | @IsTest 326 | static void test_OneToMany_List() { 327 | List accounts = new List(); 328 | List contacts = new List(); 329 | for (Integer i = 0; i < 10; ++i) { 330 | accounts.add(new Account()); 331 | contacts.add(new Contact()); 332 | contacts.add(new Contact()); 333 | } 334 | 335 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, accounts).withChildren(Contact.SObjectType, Contact.AccountId, contacts).save(false); 336 | 337 | for (Integer i = 0; i < 10; i++) { 338 | Account acc = (Account) result.get(Account.SObjectType)[i]; 339 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 340 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 341 | 342 | System.assertEquals(acc, contact1.Account); 343 | System.assertEquals(acc, contact2.Account); 344 | } 345 | } 346 | 347 | @IsTest 348 | static void test_OneToMany_List_Empty() { 349 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, new List()).withChildren(Contact.SObjectType, Contact.AccountId, new List()).save(false); 350 | 351 | List accounts = result.get(Account.SObjectType); 352 | List contacts = result.get(Contact.SObjectType); 353 | 354 | System.assertEquals(0, accounts.size()); 355 | System.assertEquals(0, contacts.size()); 356 | } 357 | 358 | // #endregion 359 | // ******************* 360 | 361 | // ******************* 362 | // #region Many to One 363 | // ******************* 364 | @IsTest 365 | static void test_ManyToOne() { 366 | // Account(10) --|------------------|--> Case(40) 367 | // |--> Contact(20) --| 368 | // prettier-ignore 369 | ATK.SaveResult result = ATK.prepare(Case.SObjectType, 40) 370 | .withParents(Contact.SObjectType, Case.ContactId, 20) 371 | .withParents(Account.SObjectType, Contact.AccountId, 10) 372 | .also(2) 373 | .withParents(Account.SObjectType, Case.AccountId) 374 | .save(false); 375 | for (Integer i = 0; i < 10; i++) { 376 | Account acc = (Account) result.get(Account.SObjectType)[i]; 377 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 378 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 379 | Case case1 = (Case) result.get(Case.SObjectType)[i * 4]; 380 | Case case2 = (Case) result.get(Case.SObjectType)[i * 4 + 1]; 381 | Case case3 = (Case) result.get(Case.SObjectType)[i * 4 + 2]; 382 | Case case4 = (Case) result.get(Case.SObjectType)[i * 4 + 3]; 383 | 384 | System.assertEquals(acc, contact1.Account); 385 | System.assertEquals(acc, contact2.Account); 386 | System.assertEquals(acc, case1.Account); 387 | System.assertEquals(acc, case2.Account); 388 | System.assertEquals(acc, case3.Account); 389 | System.assertEquals(acc, case4.Account); 390 | System.assertEquals(contact1, case1.Contact); 391 | System.assertEquals(contact1, case2.Contact); 392 | System.assertEquals(contact2, case3.Contact); 393 | System.assertEquals(contact2, case4.Contact); 394 | } 395 | } 396 | 397 | @IsTest 398 | static void test_ManyToOne_Size() { 399 | ATK.SaveResult result = ATK.prepare(Contact.SObjectType, 20).withParents(Account.SObjectType, Contact.AccountId, 10).save(false); 400 | 401 | for (Integer i = 0; i < 10; i++) { 402 | Account acc = (Account) result.get(Account.SObjectType)[i]; 403 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 404 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 405 | 406 | System.assertEquals(acc, contact1.Account); 407 | System.assertEquals(acc, contact2.Account); 408 | } 409 | } 410 | 411 | @IsTest 412 | static void test_ManyToOne_Size_Zero() { 413 | ATK.SaveResult result = ATK.prepare(Contact.SObjectType, new List()).withParents(Account.SObjectType, Contact.AccountId, new List()).save(false); 414 | 415 | List accounts = result.get(Account.SObjectType); 416 | List contacts = result.get(Contact.SObjectType); 417 | 418 | System.assertEquals(0, accounts.size()); 419 | System.assertEquals(0, contacts.size()); 420 | } 421 | 422 | @IsTest 423 | static void test_ManyToOne_List() { 424 | List accounts = new List(); 425 | List contacts = new List(); 426 | for (Integer i = 0; i < 10; ++i) { 427 | accounts.add(new Account()); 428 | contacts.add(new Contact()); 429 | contacts.add(new Contact()); 430 | } 431 | 432 | ATK.SaveResult result = ATK.prepare(Contact.SObjectType, contacts).withParents(Account.SObjectType, Contact.AccountId, accounts).save(false); 433 | 434 | for (Integer i = 0; i < 10; i++) { 435 | Account acc = (Account) result.get(Account.SObjectType)[i]; 436 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 437 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 438 | 439 | System.assertEquals(acc, contact1.Account); 440 | System.assertEquals(acc, contact2.Account); 441 | } 442 | } 443 | 444 | @IsTest 445 | static void test_ManyToOne_List_Empty() { 446 | ATK.SaveResult result = ATK.prepare(Contact.SObjectType, new List()).withParents(Account.SObjectType, Contact.AccountId, new List()).save(false); 447 | 448 | List accounts = result.get(Account.SObjectType); 449 | List contacts = result.get(Contact.SObjectType); 450 | 451 | System.assertEquals(0, accounts.size()); 452 | System.assertEquals(0, contacts.size()); 453 | } 454 | // #endregion 455 | // ******************* 456 | 457 | // ************ 458 | // #region Mock 459 | // ************ 460 | @IsTest 461 | static void test_Mock_OneToMany() { 462 | // User(10) 463 | // | 464 | // Account(10) --|------------------|--> Case(40) 465 | // |--> Contact(20) --| 466 | // prettier-ignore 467 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 468 | .withChildren(Case.SObjectType, Case.AccountId, 40) 469 | .also() 470 | .withChildren(Contact.SObjectType, Contact.AccountId, 20) 471 | .withChildren(Case.SObjectType, Case.ContactId) 472 | .also(2) 473 | .withParents(User.SObjectType, Account.OwnerId, 10) 474 | .mock(); 475 | for (Integer i = 0; i < 10; i++) { 476 | User owner = (User) result.get(User.SObjectType)[i]; 477 | Account acc = (Account) result.get(Account.SObjectType)[i]; 478 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 479 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 480 | Case case1 = (Case) result.get(Case.SObjectType)[i * 4]; 481 | Case case2 = (Case) result.get(Case.SObjectType)[i * 4 + 1]; 482 | Case case3 = (Case) result.get(Case.SObjectType)[i * 4 + 2]; 483 | Case case4 = (Case) result.get(Case.SObjectType)[i * 4 + 3]; 484 | 485 | System.assertEquals(2, acc.Contacts.size()); 486 | System.assertEquals(4, acc.Cases.size()); 487 | 488 | System.assertNotEquals(null, owner.Id); 489 | System.assertNotEquals(null, acc.Id); 490 | System.assertNotEquals(null, contact1.Id); 491 | System.assertNotEquals(null, contact2.Id); 492 | 493 | System.assertEquals(owner.Id, acc.OwnerId); 494 | System.assertEquals(contact1.Id, case1.Contact.Id); 495 | System.assertEquals(contact1.Id, case2.Contact.Id); 496 | System.assertEquals(contact2.Id, case3.Contact.Id); 497 | System.assertEquals(contact2.Id, case4.Contact.Id); 498 | 499 | System.assertEquals(owner.Id, acc.Owner.Id); 500 | System.assertEquals(acc.Id, contact1.AccountId); 501 | System.assertEquals(acc.Id, contact2.AccountId); 502 | System.assertEquals(acc.Id, case1.AccountId); 503 | System.assertEquals(acc.Id, case2.AccountId); 504 | System.assertEquals(acc.Id, case3.AccountId); 505 | System.assertEquals(acc.Id, case4.AccountId); 506 | System.assertEquals(contact1.Id, case1.ContactId); 507 | System.assertEquals(contact1.Id, case2.ContactId); 508 | System.assertEquals(contact2.Id, case3.ContactId); 509 | System.assertEquals(contact2.Id, case4.ContactId); 510 | 511 | System.assertEquals(owner, acc.Owner); 512 | System.assertEquals(contact1, case1.Contact); 513 | System.assertEquals(contact1, case2.Contact); 514 | System.assertEquals(contact2, case3.Contact); 515 | System.assertEquals(contact2, case4.Contact); 516 | } 517 | } 518 | 519 | @IsTest 520 | static void test_Mock_ManyToOne() { 521 | // User(40) 522 | // | 523 | // Account(10) --|------------------|--> Case(40) 524 | // |--> Contact(20) --| 525 | // prettier-ignore 526 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 10) 527 | .withChildren(Case.SObjectType, Case.AccountId, 40) 528 | .withParents(User.SObjectType, Case.OwnerId, 40) 529 | .field(Account.CreatedDate).repeat(Datetime.newInstance(2020, 1, 1)) 530 | .also() 531 | .withParents(Contact.SObjectType, Case.ContactId, 20) 532 | .withParents(Account.SObjectType, Contact.AccountId) 533 | .mock(); 534 | for (Integer i = 0; i < 10; i++) { 535 | Account acc = (Account) result.get(Account.SObjectType)[i]; 536 | Contact contact1 = (Contact) result.get(Contact.SObjectType)[i * 2]; 537 | Contact contact2 = (Contact) result.get(Contact.SObjectType)[i * 2 + 1]; 538 | Case case1 = (Case) result.get(Case.SObjectType)[i * 4]; 539 | Case case2 = (Case) result.get(Case.SObjectType)[i * 4 + 1]; 540 | Case case3 = (Case) result.get(Case.SObjectType)[i * 4 + 2]; 541 | Case case4 = (Case) result.get(Case.SObjectType)[i * 4 + 3]; 542 | User owner1 = (User) result.get(User.SObjectType)[i * 4]; 543 | User owner2 = (User) result.get(User.SObjectType)[i * 4 + 1]; 544 | User owner3 = (User) result.get(User.SObjectType)[i * 4 + 2]; 545 | User owner4 = (User) result.get(User.SObjectType)[i * 4 + 3]; 546 | 547 | System.assertEquals(2, acc.Contacts.size()); 548 | System.assertEquals(4, acc.Cases.size()); 549 | 550 | System.assertNotEquals(null, owner1.Id); 551 | System.assertNotEquals(null, owner2.Id); 552 | System.assertNotEquals(null, owner3.Id); 553 | System.assertNotEquals(null, owner4.Id); 554 | System.assertNotEquals(null, acc.Id); 555 | System.assertNotEquals(null, contact1.Id); 556 | System.assertNotEquals(null, contact2.Id); 557 | 558 | System.assertEquals(contact1.Id, case1.Contact.Id); 559 | System.assertEquals(contact1.Id, case2.Contact.Id); 560 | System.assertEquals(contact2.Id, case3.Contact.Id); 561 | System.assertEquals(contact2.Id, case4.Contact.Id); 562 | System.assertEquals(owner1.Id, case1.Owner.Id); 563 | System.assertEquals(owner2.Id, case2.Owner.Id); 564 | System.assertEquals(owner3.Id, case3.Owner.Id); 565 | System.assertEquals(owner4.Id, case4.Owner.Id); 566 | 567 | System.assertEquals(acc.Id, contact1.AccountId); 568 | System.assertEquals(acc.Id, contact2.AccountId); 569 | System.assertEquals(acc.Id, case1.AccountId); 570 | System.assertEquals(acc.Id, case2.AccountId); 571 | System.assertEquals(acc.Id, case3.AccountId); 572 | System.assertEquals(acc.Id, case4.AccountId); 573 | System.assertEquals(contact1.Id, case1.ContactId); 574 | System.assertEquals(contact1.Id, case2.ContactId); 575 | System.assertEquals(contact2.Id, case3.ContactId); 576 | System.assertEquals(contact2.Id, case4.ContactId); 577 | System.assertEquals(owner1.Id, case1.OwnerId); 578 | System.assertEquals(owner2.Id, case2.OwnerId); 579 | System.assertEquals(owner3.Id, case3.OwnerId); 580 | System.assertEquals(owner4.Id, case4.OwnerId); 581 | 582 | System.assertEquals(contact1, case1.Contact); 583 | System.assertEquals(contact1, case2.Contact); 584 | System.assertEquals(contact2, case3.Contact); 585 | System.assertEquals(contact2, case4.Contact); 586 | System.assertEquals(owner1, (User) case1.Owner); 587 | System.assertEquals(owner2, (User) case2.Owner); 588 | System.assertEquals(owner3, (User) case3.Owner); 589 | System.assertEquals(owner4, (User) case4.Owner); 590 | } 591 | } 592 | 593 | @IsTest 594 | static void test_Mock_SystemField() { 595 | Id fakeUserId = ATKCore.FakeId.get(User.SObjectType, 1); 596 | // prettier-ignore 597 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 9) 598 | .field(Account.CreatedById).repeat(fakeUserId) 599 | .field(Account.CreatedDate).repeat(Datetime.newInstance(2020, 1, 1)) 600 | .field(Account.LastModifiedById).repeat(fakeUserId) 601 | .field(Account.LastModifiedDate).addDays(Datetime.newInstance(2020, 1, 1), 1) 602 | .mock(); 603 | List accounts = result.get(Account.SObjectType); 604 | System.assertEquals(9, accounts.size()); 605 | for (SObject obj : accounts) { 606 | Account acc = (Account) obj; 607 | System.assertEquals(fakeUserId, acc.CreatedById); 608 | System.assertEquals(Datetime.newInstance(2020, 1, 1), acc.CreatedDate); 609 | } 610 | } 611 | 612 | @IsTest 613 | static void test_Mock_PredefinedId() { 614 | Id accountId0 = ATK.fakeId(Account.SObjectType); 615 | Id accountId1 = ATK.fakeId(Account.SObjectType); 616 | Id contactId0 = ATK.fakeId(Contact.SObjectType); 617 | Id contactId1 = ATK.fakeId(Contact.SObjectType); 618 | 619 | // prettier-ignore 620 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 2) 621 | .field(Account.Id).repeat(accountId0, (String) accountId1) 622 | .withChildren(Contact.SObjectType, Contact.AccountId, 2) 623 | .field(Contact.Id).repeat(contactId0, (String) contactId1) 624 | .mock(); 625 | System.assertEquals(accountId0, result.get(Account.SObjectType)[0].Id); 626 | System.assertEquals(accountId1, result.get(Account.SObjectType)[1].Id); 627 | System.assertEquals(contactId0, result.get(Contact.SObjectType)[0].Id); 628 | System.assertEquals(contactId1, result.get(Contact.SObjectType)[1].Id); 629 | System.assertEquals(accountId0, ((Contact) result.get(Contact.SObjectType)[0]).AccountId); 630 | System.assertEquals(accountId1, ((Contact) result.get(Contact.SObjectType)[1]).AccountId); 631 | } 632 | // #endregion 633 | // ************ 634 | 635 | @IsTest 636 | static void test_SaveResult() { 637 | // prettier-ignore 638 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 1) 639 | .withChildren(Contact.SObjectType, Contact.AccountId, 2) 640 | .withChildren(Case.SObjectType, Case.ContactId, 3) 641 | .withParents(Account.SObjectType, Case.AccountId, 4) 642 | .withChildren(Contact.SObjectType, Contact.AccountId, 5) 643 | .save(false); 644 | System.assertEquals(1, result.get(Account.SObjectType).size()); 645 | System.assertEquals(1, result.get(Account.SObjectType, 0).size()); 646 | System.assertEquals(4, result.get(Account.SObjectType, 1).size()); 647 | System.assertEquals(2, result.get(Contact.SObjectType).size()); 648 | System.assertEquals(2, result.get(Contact.SObjectType, 0).size()); 649 | System.assertEquals(5, result.get(Contact.SObjectType, 1).size()); 650 | System.assertEquals(3, result.get(Case.SObjectType).size()); 651 | 652 | System.assertEquals(5, result.getAll(Account.SObjectType).size()); 653 | System.assertEquals(7, result.getAll(Contact.SObjectType).size()); 654 | System.assertEquals(3, result.getAll(Case.SObjectType).size()); 655 | 656 | System.assertEquals(1, result.getIds(Account.SObjectType).size()); 657 | System.assertEquals(1, result.getIds(Account.SObjectType, 0).size()); 658 | System.assertEquals(4, result.getIds(Account.SObjectType, 1).size()); 659 | System.assertEquals(2, result.getIds(Contact.SObjectType).size()); 660 | System.assertEquals(2, result.getIds(Contact.SObjectType, 0).size()); 661 | System.assertEquals(5, result.getIds(Contact.SObjectType, 1).size()); 662 | System.assertEquals(3, result.getIds(Case.SObjectType).size()); 663 | 664 | System.assertEquals(5, result.getAllIds(Account.SObjectType).size()); 665 | System.assertEquals(7, result.getAllIds(Contact.SObjectType).size()); 666 | System.assertEquals(3, result.getAllIds(Case.SObjectType).size()); 667 | } 668 | 669 | @IsTest 670 | static void test_SaveResult_Also() { 671 | // prettier-ignore 672 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 1) 673 | .withChildren(Contact.SObjectType, Contact.AccountId, 2) 674 | .withChildren(Case.SObjectType, Case.ContactId, 3) 675 | .also() 676 | .withChildren(Case.SObjectType, Case.ContactId, 4) 677 | .also(2) 678 | .withChildren(Contact.SObjectType, Contact.AccountId, 5) 679 | .save(false); 680 | System.assertEquals(1, result.getAll(Account.SObjectType).size()); 681 | System.assertEquals(7, result.getAll(Contact.SObjectType).size()); 682 | System.assertEquals(7, result.getAll(Case.SObjectType).size()); 683 | 684 | System.assertEquals(1, result.getAllIds(Account.SObjectType).size()); 685 | System.assertEquals(7, result.getAllIds(Contact.SObjectType).size()); 686 | System.assertEquals(7, result.getAllIds(Case.SObjectType).size()); 687 | } 688 | 689 | @IsTest 690 | static void test_Fields() { 691 | // prettier-ignore 692 | ATK.prepare(Account.SObjectType, 0) 693 | .recordType('==Fake Record Type==') 694 | .build(new AccountEntityBuilder()) 695 | .field(Account.Name).index('Name-{0000}') 696 | .field(Account.Name).repeat('A') 697 | .field(Account.Name).repeat('A', 'B') 698 | .field(Account.Name).repeat('A', 'B', 'C') 699 | .field(Account.Name).repeat('A', 'B', 'C', 'D') 700 | .field(Account.Name).repeat('A', 'B', 'C', 'D', 'E') 701 | .field(Account.Name).repeat(new List { 'A', 'B', 'C' }) 702 | .field(Account.Name).repeatX('A', 2, 'B', 2) 703 | .field(Account.Name).repeatX('A', 2, 'B', 2, 'C', 2) 704 | .field(Account.Name).repeatX('A', 2, 'B', 2, 'C', 2, 'D', 2) 705 | .field(Account.Name).repeatX('A', 2, 'B', 2, 'C', 2, 'D', 2, 'E', 2) 706 | .field(Account.Name).repeatX(new List { 'A', 'B', 'C' }, new List { 2, 2, 2}) 707 | .field(Account.Name).repeatX(null, null) 708 | .field(Account.Name).add(1, 1) 709 | .field(Account.Name).substract(1, 1) 710 | .field(Account.Name).divide(1, 1) 711 | .field(Account.Name).multiply(1, 1) 712 | .field(Account.Name).addYears(Date.newInstance(2020, 1, 1), 1) 713 | .field(Account.Name).addMonths(Date.newInstance(2020, 1, 1), 1) 714 | .field(Account.Name).addDays(Date.newInstance(2020, 1, 1), 1) 715 | .field(Account.Name).addHours(Time.newInstance(0, 0, 0, 0), 1) 716 | .field(Account.Name).addMinutes(Time.newInstance(0, 0, 0, 0), 1) 717 | .field(Account.Name).addSeconds(Time.newInstance(0, 0, 0, 0), 1) 718 | .withParents(Contact.SObjectType, Contact.AccountId, 0) 719 | .junctionOf(Contact.AccountId, Contact.AccountId) 720 | .also() 721 | .withParents(Contact.SObjectType, Contact.AccountId, 0) 722 | .junctionOf(Contact.AccountId, Contact.AccountId, Contact.AccountId) 723 | .also() 724 | .withParents(Contact.SObjectType, Contact.AccountId, 0) 725 | .junctionOf(Contact.AccountId, Contact.AccountId, Contact.AccountId, Contact.AccountId) 726 | .also() 727 | .withParents(Contact.SObjectType, Contact.AccountId, 0) 728 | .junctionOf(Contact.AccountId, Contact.AccountId, Contact.AccountId, Contact.AccountId, Contact.AccountId) 729 | .also() 730 | .withParents(Contact.SObjectType, Contact.AccountId, 0) 731 | .junctionOf(new List()); 732 | // prettier-ignore 733 | ATK.prepare(User.SObjectType, 0) 734 | .profile('==Fake Profile==') 735 | .permissionSet('==Fake Permission Set==') 736 | .permissionSet('==Fake Permission Set 1==', '==Fake Permission Set 2==') 737 | .permissionSet('==Fake Permission Set 1==', '==Fake Permission Set 2==', '==Fake Permission Set 3==') 738 | .permissionSet(new List{ '==Fake Permission Set 1==', '==Fake Permission Set 2==' }); 739 | } 740 | 741 | class AccountEntityBuilder implements ATK.EntityBuilder { 742 | public void build(ATK.Entity accountEneity, Integer size) { 743 | } 744 | } 745 | 746 | @IsTest 747 | static void test_FakeId() { 748 | System.assertEquals(ATK.fakeId(Account.SObjectType, 1), ATK.fakeId(Account.SObjectType)); 749 | System.assertEquals(ATK.fakeId(Account.SObjectType, 2), ATK.fakeId(Account.SObjectType)); 750 | System.assertEquals(ATK.fakeId(Account.SObjectType, 3), ATK.fakeId(Account.SObjectType)); 751 | } 752 | 753 | @IsTest 754 | static void test_Save_True() { 755 | Exception exp; 756 | try { 757 | ATK.prepare(Account.SObjectType, 1).save(); 758 | } catch (Exception ex) { 759 | exp = ex; 760 | } 761 | } 762 | } 763 | -------------------------------------------------------------------------------- /apex-test-kit/main/classes/ATKTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57.0 4 | Active 5 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "ApexTestKit", 3 | "edition": "Developer", 4 | "language": "en_US", 5 | "features": ["EnableSetPasswordInApi"], 6 | "settings": { 7 | "lightningExperienceSettings": { 8 | "enableS1DesktopEnabled": true 9 | }, 10 | "mobileSettings": { 11 | "enableS1EncryptedStoragePref2": false 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /docs/bdd.md: -------------------------------------------------------------------------------- 1 | [Mockito](https://site.mockito.org/) BDD flavor has been brought into Apex Test Kit with some twists. 2 | 3 | ```java 4 | YourClass mock = (YourClass) ATK.mock(YourClass.class); 5 | // Given 6 | ATK.startStubbing(); 7 | ATK.given(mock.doSomething()).willReturn('Sth.'); 8 | ATK.stopStubbing(); 9 | 10 | // When 11 | String returnValue = mock.doSomething(); 12 | 13 | // Then 14 | System.assertEquals('Sth.', returnValue); 15 | ((ATKMockTest) ATK.then(mock).should().once()).doSomething(); 16 | ``` 17 | 18 | ## 1. Strictness 19 | 20 | ATK begins with the most strict settings by default, and developers can use setting methods to gradually relax them. Each of the following setting methods can only work within its corresponding strictness. 21 | 22 | | Strictness | `stubbedVoids()` | `defaultAnswer()` | `stubOnly()` | 23 | | ------------ | ---------------- | ----------------- | ------------------------------------------------------------ | 24 | | Strict Mode | ✓ | | ✓ | 25 | | Lenient Mode | | ✓ | ✓ | 26 | | | | | **When** step cannot track stub-only invocations
**Then** step cannot verify stub-only invocations | 27 | 28 | ### 1. 1 Strict Mode 29 | 30 | Strict mode is the default strictness enforced for all mocking activities. It helps write clean mocking codes and increase productivity. In strict mode, ATK will: 31 | 32 | 1. Fail unstubbed method invocation immediately. 33 | 2. Mark stubbed method invocation as verified implicitly for the `haveNoMoreInteractions()` calls. 34 | 3. Detect unused stubs at the end of test with `haveNoUnusedStubs()` calls. 35 | 36 | In strict mode, void methods can be treated as stubbed methods and automatically verified: 37 | 38 | ```java 39 | // 1. global level 40 | ATK.mock().withSettings().stubbedVoids(); 41 | // 2. mock level 42 | ATK.mock(YourClass.class, ATK.withSettings().stubbedVoids()); 43 | ``` 44 | 45 | ### 1.2 Lenient Mode 46 | 47 | In lenient mode, unstubbed methods will return default values, and they have to be explicitly verified for the `haveNoMoreInteractions()` calls. Use `lenient()` to enable lenient mode: 48 | 49 | ```java 50 | // 1. global level 51 | ATK.mock().withSettings().lenient(); 52 | // 2. mock level 53 | ATK.mock(YourClass.class, ATK.withSettings().lenient()); 54 | // 3. stub level 55 | ATK.lenient().given(mock.doSomething()).willReturn('Sth.'); 56 | ``` 57 | 58 | In lenient mode, default answers can be specified differently at two levels: 59 | 60 | ```java 61 | // 1. global level 62 | ATK.mock().withSettings().lenient().defaultAnswer(ATK.RETURNS_DEFAULTS); 63 | // 2. mock level 64 | ATK.mock(YourClass.class, ATK.RETURNS_DEFAULTS); 65 | ATK.mock(YourClass.class, ATK.withSettings().lenient().defaultAnswer(ATK.RETURNS_DEFAULTS)); 66 | ``` 67 | 68 | | Default Answers | Description | 69 | | ---------------------- | ------------------------------------------------------------ | 70 | | `ATK.RETURNS_DEFAULTS` | Return zeros, false, empty strings, empty collections (list, set, map), and then nulls. This is also the default behavior in lenient mode. | 71 | | `ATK.RETURNS_SELF` | Return itself whenever a method is invoked that returns a Type equal to the class or a superclass. | 72 | | `ATK.RETURNS_MOCKS` | Return ordinary values (zeros, false, empty string, empty collections) first, then it tries to return mocks. If the return type cannot be mocked (e.g. is final) then plain `null` is returned. | 73 | | Custom Answers | Custom default answers can also be supplied to the `defaultAnswer()` method, please check [Answer Customization](#answer-customization) for detail. | 74 | 75 | ## 2. Mock Settings 76 | 77 | As you have already seen, settings can be applied at three levels from high to low. Lower level settings will override the higher level settings: 78 | 79 | ``` 80 | +------------------------------+ 81 | | +------------------+ | 82 | | | +--------+ | | 83 | | global | mock | stub | | | 84 | | | +--------| | | 85 | | +------------------+ | 86 | +------------------------------+ 87 | ``` 88 | | Mock API Name | Example | 89 | | ----------------------------------------------------------- | ---------------------------------------------------------- | 90 | | `ATK.GlobalSettings ATK.mock()` | `ATK.mock().withSettings().lenient();` | 91 | | `Object ATK.mock(Type mockType)` | `ATK.mock(YourClass.class);` | 92 | | `Object ATK.mock(Type mockType, ATK.Answer defaultAnswer)` | `ATK.mock(YourClass.class, ATK.RETURNS_DEFAULTS);` | 93 | | `Object ATK.mock(Type mockType, ATK.MockSettings settings)` | `ATK.mock(YourClass.class, ATK.withSettings().lenient());` | 94 | 95 | ### 2.1 Global Level Settings 96 | 97 | Global settings are defined with `ATK.mock().withSettings()`. 98 | 99 | ```java 100 | ATK.mock().withSettings() 101 | .stubbedVoids() // In strict mode, void methods are treated as stubbed methods and automatically verified. 102 | .lenient() // Enable lenient mode. 103 | .defaultAnswer(ATK.RETURNS_DEFAULTS) // Specify default answers for lenient mode. 104 | .stubOnly() // In either strict or lenient mode, any interactions can neither be tracked nor verified. 105 | .verbose(); // For development/debug purpose to print verbose messages. 106 | ``` 107 | 108 | ### 2.2 Mock Level Settings 109 | 110 | ```java 111 | YourClass mock = (YourClass) ATK.mock(YourClass.class, ATK.withSettings() 112 | .name('mock') // This name is used in exception message, otherwise "[YourClass]" is used as instance name. 113 | .stubbedVoids() // In strict mode, void methods are treated as stubbed methods and automatically verified. 114 | .lenient() // Enable lenient mode. 115 | .defaultAnswer(ATK.RETURNS_DEFAULTS) // Specify default answers for lenient mode. 116 | .stubOnly() // In either strict or lenient mode, any interactions can neither be tracked nor verified. 117 | .verbose()); // For development/debug purpose to print verbose messages. 118 | ``` 119 | 120 | 121 | ### 2.3 Stub Level Settings 122 | 123 | `ATK.lenient()` is the only stub level setting used to bypass the strict mode. 124 | 125 | ```java 126 | ATK.lenient().given(mock.doWithInteger(1)).willReturn('one'); 127 | ((YourClass) ATK.lenient().willReturn('one').given(mock)).doWithInteger(1); 128 | ``` 129 | 130 | ## 3. Given Steps 131 | 132 | ### 3.1 Five Rules 133 | 134 | Please consider the following five rules carefully before define stubs in the given steps. 135 | 136 | ```java 137 | YourClass mock = (YourClass) ATK.mock(YourClass.class); 138 | 139 | // 1. Given Statements must defined between ATK.startStubbing() and ATK.stopStubbing(). 140 | ATK.startStubbing(); 141 | 142 | // 2. The following two flavors define the same behavior. 143 | ATK.given(mock.doWithInteger(1)).willReturn('one'); // 2-1. Flavor 1 144 | ((YourClass) ATK.willReturn('one').given(mock)).doWithInteger(1); // 2-2. Flavor 2 145 | 146 | // 3. Only the second flavor can be used for void methods. 147 | ((YourClass) ATK.willDoNothing().given(mock)).doVoidReturn(); 148 | 149 | // 4. Matchers can be used to define the stubs with arbitrary arguments. 150 | ATK.given(mock.doWithInteger(ATK.anyInteger())).willReturn('any'); 151 | ATK.given(mock.doWithInteger(ATK.gte(1)).willReturn('>=1'); 152 | 153 | // 5. Latter Stub with same arguments can override the former one. 154 | ATK.given(mock.doWithInteger(1).willReturn('one'); // 5-1. Cannot be matched 155 | ATK.given(mock.doWithInteger(ATK.gte(1)).willReturn('>=1'); // 5-2. Will be matched 156 | 157 | ATK.stopStubbing(); 158 | ``` 159 | 160 | ### 3.2 Answers 161 | 162 | Here are the APIs to define answers for the stubs in the give steps. 163 | 164 | | API Name | Description | 165 | | ------------------------------- | ------------------------------------------------------------ | 166 | | `willReturn(Object value)` | Return any value that compatible with the target method return type. | 167 | | `willAnswer(ATK.Answer answer)` | Return a customized answer dynamically according to conditions such as arguments and return type. | 168 | | `willThrow(Exception exp)` | Throw the exception when target method is called. | 169 | | `willDoNothing()` | Return `null`. Supposed to be called with void methods only. | 170 | 171 | #### Answer Chaining 172 | 173 | Answers can be chained for a particular stub, and their values will be returned one by one in the defining order for each interaction. If the answers are exhausted, `null` will be returned instead. Here is an example: 174 | 175 | ```java 176 | YourClass mock = (YourClass) ATK.mock(YourClass.class); 177 | ATK.startStubbing(); 178 | ATK.given(mock.doWithInteger(1)).willReturn('one').willReturn('another one'); 179 | ATK.stopStubbing(); 180 | 181 | System.assertEquals('one', mock.doWithInteger(1)); 182 | System.assertEquals('another one', mock.doWithInteger(1)); 183 | System.assertEquals(null, mock.doWithInteger(1)); 184 | ``` 185 | 186 | #### Answer Customization 187 | 188 | Customized answers can be supplied to both `defaultAnswer()` and `willAnswer()` methods. 189 | 190 | ```java 191 | public class YourCustomAnswer implements ATK.Answer { 192 | public Object answer(ATK.Invocation invocation) { 193 | // ... 194 | } 195 | } 196 | 197 | ATK.mock(YourClass.class, ATK.withSettings().defaultAnswer(new YourCustomAnswer())); 198 | ATK.given(mock.doWithInteger(1)).willAnswer(new YourCustomAnswer()); // mock is created elsewhere 199 | ``` 200 | 201 | | `ATK.Invocation` Properties | Description | 202 | | --------------------------- | ------------------------------------------------------------ | 203 | | `Object mock` | The mock object. | 204 | | `Type mockType` | The mock type. | 205 | | `ATK.Method method` | The method metadata, also reference to `ATK.method` properties below. | 206 | | `List arguments` | The `Arguments` passed into the invocation. | 207 | 208 | | `ATK.Method` Properties | Description | 209 | | ------------------------- | --------------------------- | 210 | | `String name` | The method name. | 211 | | `Type returnType` | The method return type. | 212 | | `List paramNames` | The method parameter names. | 213 | | `List paramTypes` | The method parameter types. | 214 | 215 | ## 4. Then Steps 216 | 217 | This is the only flavor to declare then statements. And as the same as given statements, argument matchers can be used as well. 218 | 219 | ```java 220 | ((YourClass) ATK.then(mock).should().once()).doWithInteger(1); 221 | ((YourClass) ATK.then(mock).should().once()).doWithInteger(ATK.anyInteger()); 222 | ``` 223 | 224 | ### 4.1 Verification Mode 225 | 226 | | API Name | Alias To | Description | 227 | | -------------------- | ------------ | ------------------------------------------------ | 228 | | `never()` | `times(0)` | Verifies that interaction did not happen. | 229 | | `once()` | `times(1)` | Verifies that interaction happened exactly once. | 230 | | `times(Integer n)` | | Allows verifying exact number of invocations. | 231 | | `atLeastOnce()` | `atLeast(1)` | Allows at-least-once verification. | 232 | | `atLeast(Integer n)` | | Allows at-least-n verification. | 233 | | `atMostOnce()` | `atMost(1)` | Allows at-most-once verification. | 234 | | `atMost(Integer n)` | | Allows at-most-n verification. | 235 | 236 | | API Name | Description | Example | 237 | | -------------------------- | ------------------------------------------------------------ | -------------------------------------------------- | 238 | | `haveNoInteractions()` | Fail if there are any interactions with the mock in when steps. | `ATK.then(mock).should().haveNoInteractions()` | 239 | | `haveNoMoreInteractions()` | Fail if there are any interactions unverified with the mock. | `ATK.then(mock).should().haveNoMoreInteractions()` | 240 | | `haveNoUnusedStubs()` | Fail if there are any unused/unmatched stubs in when steps. | `ATK.then(mock).should().haveNoUnusedStubs()` | 241 | 242 | ### 4.2 In-Order Verification 243 | 244 | ```java 245 | YourClass mock = (YourClass) ATK.mock(YourClass.class, ATK.withSettings().lenient()); 246 | 247 | mock.doWithInteger(1); 248 | mock.doWithInteger(1); 249 | mock.doWithInteger(2); 250 | mock.doWithInteger(1); 251 | 252 | ATK.InOrder inOrder = ATK.InOrder(mock); 253 | 254 | ((YourClass) ATK.then(mock).should(inOrder).times(2)).doWithInteger(1); 255 | ((YourClass) ATK.then(mock).should(inOrder).times(1)).doWithInteger(2); 256 | ((YourClass) ATK.then(mock).should(inOrder).times(1)).doWithInteger(1); 257 | 258 | ATK.then(mock).should(inOrder).haveNoMoreInteractions(); 259 | ``` 260 | 261 | Not all verification modes are supported by in-order verifications. Please stick to the following verification modes with `should(ATK.InOrder inOrder)`: 262 | 263 | | API Name | Descriptions | 264 | | -------------------------- | ------------------------------------------------------------ | 265 | | `never()` | Verifies that interaction did not happen. | 266 | | `once()` | Verifies that interaction happened exactly once. | 267 | | `times(Integer n)` | Allows verifying exact number of invocations. | 268 | | `calls(Integer n)` | Non-greedy verifications. Check Mockito wiki [Greedy Algorithm of Verification InOrder](https://github.com/mockito/mockito/wiki/Greedy-algorithm-of-verification-InOrder) for detail. | 269 | | `haveNoMoreInteractions()` | In-order verifications are tracked in a different context, so even in strict mode, all interactions should be exhausted with verifications explicitly. | 270 | 271 | ### 4.3 Assert Messages 272 | 273 | Here are sample assertion messages. The generated method signature could be different than the one defined in the test classes, such as all exact values will be replaced by `ATK.eq()` matchers. In future this is an area I will continuously improve, to help developers better understand the message contexts. 274 | 275 | ``` 276 | Expected "[ATKMockTest].doWithIntegers(ATK.eq(1))" to be called 1 time(s). But has been called 0 time(s). 277 | Expected "[ATKMockTest].doWithIntegers(ATK.eq(1))" to be called at least 3 time(s). But has been called 0 time(s). 278 | Expected "[ATKMockTest].doWithIntegers(ATK.eq(1))" to be called at most 3 time(s). But has been called 0 time(s). 279 | ``` 280 | 281 | ## 5. Matchers 282 | 283 | Please don't mix exact values and matchers in one given statement, either use exact values or matchers for all arguments. 284 | 285 | ```java 286 | // Correct 287 | ATK.given(mock.doWithIntegers(1, 2, 3)).willReturn('1, 2, 3'); 288 | ATK.given(mock.doWithIntegers(ATK.eqInteger(1), ATK.eqInteger(2), ATK.eqInteger(3)).willReturn('1, 2, 3'); 289 | 290 | // Wrong 291 | ATK.given(mock.doWithIntegers(1, 2, ATK.eqInteger(3)).willReturn('1, 2, 3'); 292 | ``` 293 | 294 | ### 5.1 Type Matchers 295 | 296 | #### Any Types 297 | 298 | Please supply exactly the same type used by the matched argument, neither ancestor nor descendent types are allowed. 299 | 300 | | API Name | Description | Example | 301 | | ---------------------------- | ---------------------------------------------------- | ---------------------------- | 302 | | `Object any()` | Matches **anything**, including nulls. | `ATK.any()` | 303 | | `Object any(Type type)` | Matches any object of given type, excluding nulls. | `ATK.any(String.class)` | 304 | | `Object nullable(Type type)` | Argument that is either `null` or of the given type. | `ATK.nullable(String.class)` | 305 | 306 | #### Primitives 307 | 308 | | API Name | Alias To | Description | 309 | | ------------------------ | ------------------------ | ------------------------------------------------------------ | 310 | | `Integer anyInteger()` | `ATK.any(Integer.class)` | Only allow valued `Integer`, excluding nulls. | 311 | | `Long anyLong()` | `ATK.any(Long.class)` | Only allow valued `Long`, excluding nulls. | 312 | | `Double anyDouble()` | `ATK.any(Double.class)` | Only allow valued `Double`, excluding nulls. | 313 | | `Decimal anyDecimal()` | `ATK.any(Decimal.class)` | Only allow valued `Decimal`, excluding nulls. | 314 | | `Date anyDate()` | `ATK.any(Date.class)` | Only allow valued `Date`, excluding nulls. | 315 | | `Datetime anyDatetime()` | `ATK.any(Datetime.class)` | Only allow valued `Datetime`, excluding nulls. | 316 | | `Time anyTime()` | `ATK.any(Time.class)` | Only allow valued `Time`, excluding nulls. | 317 | | `Id anyId()` | `ATK.any(Id.class)` | Only allow valued `Id`, excluding nulls. | 318 | | `String anyString()` | `ATK.any(String.class)` | Only allow valued `String`, excluding nulls. | 319 | | `Boolean anyBoolean()` | `ATK.any(Boolean.class)` | Only allow valued `Boolean`, excluding nulls. | 320 | 321 | #### Collections 322 | 323 | | API Name | Description | Example | 324 | | ---- | ---- | ---- | 325 | | `List anyList()` | Only allow non-null `List`. | `ATK.anyList()` | 326 | | `Object anySet()` | Only allow non-null `Set`. | `ATK.anySet()` | 327 | | `Object anyMap()` | Only allow non-null `Map`. | `ATK.anyMap()` | 328 | | `SObject anySObject()` | Only allow non-null `SObject`. | `ATK.anySObject()` | 329 | | `List anySObjectList()` | Only allow non-null `List`, such as `List` etc. | `ATK.anySObjectList()` | 330 | 331 | ### 5.2 Value Matchers 332 | 333 | #### References 334 | | API Name | Description | 335 | | ---- | ---- | 336 | |`Object isNull()`| `null` argument. | 337 | |`Object isNotNull()`| Not `null` argument. | 338 | |`Object same(Object value)`| Object argument that is the same as the given value. | 339 | 340 | #### Equals 341 | 342 | | API Name | Alias To | Description | 343 | | ---- | ---- | ---- | 344 | |`Object eq(Object value)`| | Object argument that is equal to the given value. | 345 | |`Integer eqInteger(Integer value)`| `(Integer) ATK.eq(123)` | `Integer` argument that is equal to the given value. | 346 | |`Long eqLong(Long value)`| `(Long) ATK.eq(123L)` | `Long` argument that is equal to the given value. | 347 | |`Double eqDouble(Double value)`| `(Double) ATK.eq(123.0D)` | `Double` argument that is equal to the given value. | 348 | |`Decimal eqDecimal(Decimal value)`| `(Decimal) ATK.eq(123.0)` | `Decimal` argument that is equal to the given value. | 349 | |`Date eqDate(Date value)`| `(Date) ATK.eq(Date.today())` | `Date` argument that is equal to the given value. | 350 | |`Datetime eqDatetime(Datetime value)`| `(Datetime) ATK.eq(Datetime.now())` | `Datetime` argument that is equal to the given value. | 351 | |`Time eqTime(Time value)`| `(Time) ATK.eq(Time.newInstance(0, 0, 0, 0))` | `Time` argument that is equal to the given value. | 352 | |`Id eqId(Id value)`| `(Id) ATK.eq(accountId)` | `Id` argument that is equal to the given value. | 353 | |`String eqString(String value)`| `(String) ATK.eq('In Progress')` | `String` argument that is equal to the given value. | 354 | |`Boolean eqBoolean(Boolean value)`| `(Boolean) ATK.eq(true)` | `Boolean` argument that is equal to the given value. | 355 | 356 | #### Non Equals 357 | 358 | | API Name | Alias To | Description | 359 | | ---- | ---- | ---- | 360 | |`Object ne(Object value)`| | Object argument that is not equal to the given value. | 361 | |`Integer neInteger(Integer value)`| `(Integer) ATK.ne(123)` | `Integer` argument that is not equal to the given value. | 362 | |`Long neLong(Long value)`| `(Long) ATK.ne(123L)` | `Long` argument that is not equal to the given value. | 363 | |`Double neDouble(Double value)`| `(Double) ATK.ne(123.0D)` | `Double` argument that is not equal to the given value. | 364 | |`Decimal neDecimal(Decimal value)`| `(Decimal) ATK.ne(123.0)` | `Decimal` argument that is not equal to the given value. | 365 | |`Date neDate(Date value)`| `(Date) ATK.ne(Date.today())` | `Date` argument that is not equal to the given value. | 366 | |`Datetime neDatetime(Datetime value)`| `(Datetime) ATK.ne(Datetime.now())` | `Datetime` argument that is not equal to the given value. | 367 | |`Time neTime(Datetime value)`| `(Time) ATK.ne(Time.newInstance(0, 0, 0, 0))` | `Time` argument that is not equal to the given value. | 368 | |`Id neId(Id value)`| `(Id) ATK.ne(accountId)` | `Id` argument that is not equal to the given value. | 369 | |`String neString(String value)`| `(String) ATK.ne('In Progress')` | `String` argument that is not equal to the given value. | 370 | |`Boolean neBoolean(Boolean value)`| `(Boolean) ATK.ne(true)` | `Boolean` argument that is not equal to the given value. | 371 | 372 | #### Comparisons 373 | 374 | Comparison matchers are overloaded with the following primitive types: `Integer`, `Long`, `Double`, `Decimal`, `Date`, `Datetime`, `Time`, `Id`, `String`. 375 | 376 | | API Name | Description | Example | 377 | | ---- | ---- | ---- | 378 | | `gt(Object value)` | Greater than the given value. | `ATK.gt(10L)` | 379 | | `gte(Object value)` | Greater than or equal to the given value. | `ATK.gte(10.0D)` | 380 | | `lt(Object value)` | Less than the given value. | `ATK.lt(10.0)` | 381 | | `lte(Object value)` | Less than or equal to the given value. | `ATK.lte(Date.today())` | 382 | | `between(Object min, Object max)` | Between the given values. `min` and `max` values are inclusive, same behavior as the `BETWEEN` keyword used in SQL. | `ATK.between(1, 10)` | 383 | | `between(Object min, Object max, Boolean inclusive)` | Use `inclusive = false` to exclude boundary values. | `ATK.between(1, 10, true)` | 384 | | `between(Object min, Boolean minInclusive, Object max, Boolean maxInclusive)` | Finer control to the `min` and `max` inclusive behaviors. | `ATK.between(1, false, 10, true)` | 385 | 386 | #### Strings 387 | 388 | | API Name | Example | 389 | | ---- | ---- | 390 | | `String isBlank()` | `ATK.isBlank()` | 391 | | `String isNotBlank()` | `ATK.isNotBlank()` | 392 | | `String contains(String value)` | `ATK.contains('abc')` | 393 | | `String startsWith(String value)` | `ATK.startsWith('abc')` | 394 | | `String endsWith(String value)` | `ATK.endsWith('abc')` | 395 | | `String matches(String regexp)` | `ATK.matches('^[2-9]\\d\'{\'2\'}\'-\\d\'{\'3\'}\'-\\d\'{\'4\'}\'$')` | 396 | 397 | 398 | #### sObjects 399 | | API Name | Example | 400 | | ---- | ---- | 401 | | `SObject sObjectWithId(Id value)` | `ATK.sObjectWithId(accountId)` | 402 | | `SObject sObjectWithName(String value)` | `ATK.sObjectWithName('Salesforce')` | 403 | | `SObject sObjectWith(SObjectField field, Object value)` | `ATK.sObjectWith(Account.Name, 'Salesforce')` | 404 | | `SObject sObjectWith(Map value)` | `ATK.sObjectWith(new Map {})` | 405 | | `LIst sObjectListWith(SObjectField field, Object value)` | `ATK.sObjectListWith(Opportunity.StageName, 'Open')` | 406 | | `LIst sObjectListWith(Map value)` | `ATK.sObjectListWith(new Map {})` | 407 | | `LIst sObjectListWith(List> value, Boolean inOrder)` | `ATK.sObjectListWith(new List>{})` | 408 | 409 | ### 5.3 Logical Matchers 410 | 411 | ```java 412 | ATK.given(mock.doWithInteger((Integer) ATK.allOf(ATK.gt(1), ATK.lt(10)))).willReturn('arg > 1 AND arg < 10'); 413 | ``` 414 | 415 | #### AND 416 | 417 | | API Name | Description | 418 | | ---- | ---- | 419 | | `Object allOf(Object arg1, Object arg2)` | Logical AND operator. | 420 | | `Object allOf(Object arg1, Object arg2, Object arg3)` | Logical AND operator. | 421 | | `Object allOf(Object arg1, Object arg2, Object arg3, Object arg4)` | Logical AND operator. | 422 | | `Object allOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)` | Logical AND operator. | 423 | | `Object allOf(List args)` | Logical AND operator. | 424 | 425 | #### OR 426 | 427 | | API Name | Description | 428 | | ------------------------------------------------------------ | -------------------- | 429 | | `Object anyOf(Object arg1, Object arg2)` | Logical OR operator. | 430 | | `Object anyOf(Object arg1, Object arg2, Object arg3)` | Logical OR operator. | 431 | | `Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4)` | Logical OR operator. | 432 | | `Object anyOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)` | Logical OR operator. | 433 | | `Object anyOf(List args)` | Logical OR operator. | 434 | 435 | #### NOR 436 | 437 | | API Name | Description | 438 | | ------------------------------------------------------------ | --------------------- | 439 | | `Object isNot(Object arg1)` | Logical NOT operator. | 440 | | `Object noneOf(Object arg1, Object arg2)` | Logical NOR operator. | 441 | | `Object noneOf(Object arg1, Object arg2, Object arg3)` | Logical NOR operator. | 442 | | `Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4)` | Logical NOR operator. | 443 | | `Object noneOf(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)` | Logical NOR operator. | 444 | | `Object noneOf(List args)` | Logical NOR operator. | 445 | 446 | ------ 447 | 448 | ## Happy Hacking! 449 | -------------------------------------------------------------------------------- /docs/images/deploy-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apexfarm/ApexTestKit/607e6577c9c83d0dc874af83d1c67b2962508dcc/docs/images/deploy-button.png -------------------------------------------------------------------------------- /docs/images/mock-relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apexfarm/ApexTestKit/607e6577c9c83d0dc874af83d1c67b2962508dcc/docs/images/mock-relationship.png -------------------------------------------------------------------------------- /docs/images/sales-objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apexfarm/ApexTestKit/607e6577c9c83d0dc874af83d1c67b2962508dcc/docs/images/sales-objects.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Salesforce App", 6 | "scripts": { 7 | "lint": "eslint **/{aura,lwc}/**", 8 | "test": "npm run test:unit", 9 | "test:unit": "sfdx-lwc-jest", 10 | "test:unit:watch": "sfdx-lwc-jest --watch", 11 | "test:unit:debug": "sfdx-lwc-jest --debug", 12 | "test:unit:coverage": "sfdx-lwc-jest --coverage", 13 | "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 14 | "prettier:verify": "prettier --list-different \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 15 | "postinstall": "husky install", 16 | "precommit": "lint-staged" 17 | }, 18 | "devDependencies": { 19 | "@lwc/eslint-plugin-lwc": "^1.1.2", 20 | "@prettier/plugin-xml": "^2.0.1", 21 | "@salesforce/eslint-config-lwc": "^3.2.3", 22 | "@salesforce/eslint-plugin-aura": "^2.0.0", 23 | "@salesforce/eslint-plugin-lightning": "^1.0.0", 24 | "@salesforce/sfdx-lwc-jest": "^1.1.0", 25 | "eslint": "^8.11.0", 26 | "eslint-plugin-import": "^2.25.4", 27 | "eslint-plugin-jest": "^26.1.2", 28 | "husky": "^7.0.4", 29 | "lint-staged": "^12.3.7", 30 | "prettier": "^2.6.0", 31 | "prettier-plugin-apex": "^1.10.0" 32 | }, 33 | "lint-staged": { 34 | "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ 35 | "prettier --write" 36 | ], 37 | "**/{aura,lwc}/**": [ 38 | "eslint" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/apex/benchmark.apex: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla Database Insert 3 | */ 4 | Savepoint sp = Database.setSavepoint(); 5 | Datetime startTime = Datetime.now(); 6 | Integer startCPU = Limits.getCpuTime(); 7 | List accounts = new List(); 8 | for (Integer i = 0; i < 1000; i++) { 9 | accounts.add(new Account( 10 | Name = 'Name-' + i 11 | )); 12 | } 13 | insert accounts; 14 | Datetime endTime = Datetime.now(); 15 | Integer endCPU = Limits.getCpuTime(); 16 | System.debug(endTime.getTime() - startTime.getTime()); 17 | System.debug('CPU: ' + (endCPU - startCPU)); 18 | Database.rollback(sp); 19 | 20 | /** 21 | * ApexTextKit Save 22 | */ 23 | sp = Database.setSavepoint(); 24 | startTime = Datetime.now(); 25 | startCPU = Limits.getCpuTime(); 26 | ATK.prepare(Account.SObjectType, 1000) 27 | .field(Account.Name).index('Name-{0000}') 28 | .field(Account.Phone).index('+86 186 7777 {0000}') 29 | .field(Account.Description).repeat('Description...') 30 | .save(); 31 | endTime = Datetime.now(); 32 | endCPU = Limits.getCpuTime(); 33 | System.debug(endTime.getTime() - startTime.getTime()); 34 | System.debug('CPU: ' + (endCPU - startCPU)); 35 | Database.rollback(sp); 36 | 37 | /** 38 | * ApexTextKit Mock 39 | */ 40 | sp = Database.setSavepoint(); 41 | startTime = Datetime.now(); 42 | startCPU = Limits.getCpuTime(); 43 | ATK.prepare(Account.SObjectType, 1000) 44 | .field(Account.Name).index('Name-{0000}') 45 | .field(Account.CreatedById).repeat(ATKCore.FAKEID.get(User.SObjectType, 1)) 46 | .field(Account.CreatedDate).repeat(Datetime.newInstance(2020, 1, 1)) 47 | .field(Account.LastModifiedById).repeat(ATKCore.FAKEID.get(User.SObjectType, 1)) 48 | .field(Account.LastModifiedDate).repeat(Datetime.newInstance(2020, 1, 1)) 49 | .mock(); 50 | endTime = Datetime.now(); 51 | endCPU = Limits.getCpuTime(); 52 | System.debug(endTime.getTime() - startTime.getTime()); 53 | System.debug('CPU: ' + (endCPU - startCPU)); 54 | Database.rollback(sp); -------------------------------------------------------------------------------- /scripts/apex/demo-campaign.apex: -------------------------------------------------------------------------------- 1 | // 1. Enable FLS of HierarchyNumberOfContacts(Contacts in Hierarchy) of Campaign 2 | 3 | Savepoint sp = Database.setSavepoint(); 4 | 5 | CampaignBuilder campaignBuilder = new CampaignBuilder(); 6 | ATK.SaveResult result = ATK.prepare(Campaign.SObjectType, 4) 7 | .build(campaignBuilder) 8 | .withChildren(Campaign.SObjectType, Campaign.ParentId, 4) 9 | .build(campaignBuilder) 10 | .withChildren(Campaign.SObjectType, Campaign.ParentId, 4) 11 | .build(campaignBuilder) 12 | .withChildren(Campaign.SObjectType, Campaign.ParentId, 4) 13 | .build(campaignBuilder) 14 | .withChildren(Campaign.SObjectType, Campaign.ParentId, 4) 15 | .build(campaignBuilder) 16 | .save(true); 17 | 18 | convertLeads(result); 19 | 20 | Database.rollback(sp); 21 | 22 | class CampaignBuilder implements ATK.EntityBuilder { 23 | public void build(ATK.Entity campaignEntity, Integer size) { 24 | campaignEntity 25 | .field(Campaign.Type).repeat('Partners') 26 | .field(Campaign.Name).index('Name-{0000}') 27 | .field(Campaign.StartDate).repeat(Date.newInstance(2020, 1, 1)) 28 | .field(Campaign.EndDate).repeat(Date.newInstance(2020, 1, 1).addMonths(1)) 29 | .withChildren(CampaignMember.SObjectType, CampaignMember.CampaignId, size * 2) 30 | .withParents(Lead.SObjectType, CampaignMember.LeadId, size * 2) 31 | .field(Lead.Company).index('Name-{0000}') 32 | .field(Lead.LastName).index('Name-{0000}') 33 | .field(Lead.Email).index('test.user+{0000}@email.com') 34 | .field(Lead.MobilePhone).index('+86 186 7777 {0000}') 35 | .also(2); // Reset the current working SObjectType back to Campaign 36 | } 37 | } 38 | 39 | public void convertLeads(ATK.SaveResult result) { 40 | LeadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted=true LIMIT 1]; 41 | 42 | for (Integer i = 4; i > -1; i--) { 43 | List leads = result.get(Lead.SObjectType, i); 44 | for (SObject obj : leads) { 45 | Lead lead = (Lead)obj; 46 | Database.LeadConvert lc = new Database.LeadConvert(); 47 | lc.setLeadId(lead.Id); 48 | lc.setDoNotCreateOpportunity(true); 49 | lc.setConvertedStatus(convertStatus.MasterLabel); 50 | Database.LeadConvertResult lcr = Database.convertLead(lc); 51 | System.assert(lcr.isSuccess()); 52 | } 53 | 54 | Map campaigns = new Map(result.get(Campaign.SObjectType, i)); 55 | for (Campaign camp : [ 56 | SELECT 57 | NumberOfContacts, 58 | HierarchyNumberOfContacts 59 | FROM Campaign 60 | WHERE Id IN :campaigns.keySet()]) { 61 | System.assertEquals(2, camp.NumberOfContacts); 62 | System.assertEquals(2 * (5 - i), camp.HierarchyNumberOfContacts); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /scripts/apex/demo-cases.apex: -------------------------------------------------------------------------------- 1 | Savepoint sp = Database.setSavepoint(); 2 | 3 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 200) 4 | .field(Account.Name).index('Name-{0000}') 5 | .withChildren(Contact.SObjectType, Contact.AccountId, 400) 6 | .field(Contact.LastName).index('Name-{0000}') 7 | .field(Contact.Email).index('test.user+{0000}@email.com') 8 | .field(Contact.MobilePhone).index('+86 186 7777 {0000}') 9 | .field(Contact.Birthdate).addDays(Date.newInstance(2020, 1, 1), 1) 10 | .withChildren(Case.SObjectType, Case.ContactId, 800) // Parent Cases 11 | .withParents(Account.SObjectType, Case.AccountId) 12 | .also() 13 | .withParents(Case.SObjectType, Case.ParentId, 800) // Child Cases 14 | .withParents(Account.SObjectType, Case.AccountId) 15 | .also() 16 | .withParents(Contact.SObjectType, Case.ContactId) 17 | .save(true); 18 | 19 | List accounts = result.get(Account.SObjectType); 20 | List contacts = result.get(Contact.SObjectType); 21 | List cases = result.get(Case.SObjectType); 22 | 23 | for (Integer i = 0; i< 200; ++i) { 24 | System.assertEquals(accounts[i].Id, contacts[i*2].get('AccountId')); 25 | System.assertEquals(accounts[i].Id, contacts[i*2+1].get('AccountId')); 26 | 27 | System.assertEquals(accounts[i].Id, cases[i*4].get('AccountId')); 28 | System.assertEquals(accounts[i].Id, cases[i*4+1].get('AccountId')); 29 | System.assertEquals(accounts[i].Id, cases[i*4+2].get('AccountId')); 30 | System.assertEquals(accounts[i].Id, cases[i*4+3].get('AccountId')); 31 | 32 | System.assertEquals(contacts[i*2].Id, cases[i*4].get('ContactId')); 33 | System.assertEquals(contacts[i*2].Id, cases[i*4+1].get('ContactId')); 34 | System.assertEquals(contacts[i*2+1].Id, cases[i*4+2].get('ContactId')); 35 | System.assertEquals(contacts[i*2+1].Id, cases[i*4+3].get('ContactId')); 36 | } 37 | 38 | Database.rollback(sp); 39 | -------------------------------------------------------------------------------- /scripts/apex/demo-consumer.apex: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. Install https://github.com/apexfarm/ApexTestKit 3 | * 4 | * 2. Targeted Relationships: 5 | * Account(2) <= Retail Store(4) <= In-store Location(8) <= Store Product(16) => Product2(4) 6 | * 7 | * 2. Expected Distributions: 8 | * |---------------------------------------------------|---------------------------------------------------| 9 | * | Account 1 | Account 2 | 10 | * | Contact 1 | Contact 3 | 11 | * | Contact 2 | Contact 4 | 12 | * |-------------------------|-------------------------|-------------------------|-------------------------| 13 | * | Store Group 1 | Store Group 1 | Store Group 2 | Store Group 2 | 14 | * | Retail Store 1 | Retail Store 2 | Retail Store 3 | Retail Store 4 | 15 | * | In-store Location 1 | In-store Location 3 | In-store Location 5 | In-store Location 7 | 16 | * | Product 1 | Product 1 | Product 1 | Product 1 | 17 | * | Product 2 | Product 2 | Product 2 | Product 2 | 18 | * | In-store Location 2 | In-store Location 4 | In-store Location 6 | In-store Location 8 | 19 | * | Product 3 | Product 3 | Product 3 | Product 3 | 20 | * | Product 4 | Product 4 | Product 4 | Product 4 | 21 | * |-------------------------|-------------------------|-------------------------|-------------------------| 22 | */ 23 | 24 | Savepoint sp = Database.setSavepoint(); 25 | Datetime startTime = Datetime.now(); 26 | 27 | List priceBooks = new List{ 28 | [SELECT Id, Name FROM PriceBook2 WHERE IsStandard = TRUE][0], 29 | new PriceBook2(Name = 'Local Price Book', IsActive = true) 30 | }; 31 | 32 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 2) 33 | .field(Account.Name).index('Account {000}') 34 | .withChildren(Contact.SObjectType, Contact.AccountId, 4) 35 | .field(Contact.LastName).index('Contact {000}') 36 | .also() 37 | // =============Retail Stores============= 38 | .withChildren(RetailStore.SObjectType, RetailStore.AccountId, 4) 39 | .field(RetailStore.Name).index('Retail Store {000}') 40 | .withChildren(InstoreLocation.SObjectType, InstoreLocation.RetailStoreId, 8) 41 | .field(InstoreLocation.Name).index('In-store Location {000}') 42 | .field(InstoreLocation.InStoreLocationType).repeat('Shelf', 'Backroom') 43 | .field(InstoreLocation.Category).repeat('Aisle', 'Backroom Storage') 44 | .withChildren(StoreProduct.SObjectType, StoreProduct.InstoreLocationId, 16) 45 | .junctionOf(StoreProduct.InstoreLocationId, StoreProduct.ProductId) 46 | .field(StoreProduct.StartDate).repeat(Date.newInstance(2020, 1, 1)) 47 | .field(StoreProduct.DefaultOrderQuantity).repeat(50) 48 | .field(StoreProduct.IsFavorite).repeat(true, false) 49 | .field(StoreProduct.DisplayOrder).repeat(1, 2) 50 | // =============Products============= 51 | .withParents(Product2.SObjectType, StoreProduct.ProductId, 4) 52 | .field(Product2.Name).index('Product {000}') 53 | .field(Product2.IsActive).repeat(true) 54 | .withChildren(PricebookEntry.SObjectType, PricebookEntry.Product2Id, 8) 55 | .junctionOf(PricebookEntry.Product2Id, PricebookEntry.Pricebook2Id) 56 | .field(PricebookEntry.IsActive).repeat(true) 57 | .field(PricebookEntry.UnitPrice).repeat(9.99) 58 | .withParents(Pricebook2.SObjectType, PricebookEntry.Pricebook2Id, priceBooks) 59 | .also(5) 60 | .withParents(RetailLocationGroup.SObjectType, RetailStore.RetailLocationGroupId, 2) 61 | .field(RetailLocationGroup.Name).index('Store Group {000}') 62 | // =============Store KPIs============= 63 | // 2 Groups * 2 Indicators * 4 Products 64 | .withChildren(RetailStoreKpi.SObjectType, RetailStoreKpi.RetailStoreGroupId, 16) 65 | .junctionOf(RetailStoreKpi.RetailStoreGroupId, RetailStoreKpi.AssessmentIndDefinitionId, RetailStoreKpi.ProductId) 66 | .field(RetailStoreKpi.InStoreLocationCategory).repeatX('Aisle', 4, 'Backroom Storage', 4) 67 | .field(RetailStoreKpi.KpiType).repeat('Inventory') 68 | .field(RetailStoreKpi.StartDate).repeat(Date.newInstance(2020, 1, 1)) 69 | .field(RetailStoreKpi.TargetIntegerValue).repeat(10, null) 70 | .field(RetailStoreKpi.TargetDecimalValue).repeat(null, 9.9) 71 | .withParents(AssessmentIndicatorDefinition.SObjectType, RetailStoreKpi.AssessmentIndDefinitionId, 2) 72 | .field(AssessmentIndicatorDefinition.Name).repeat('Inventory Check | Inventory Count', 'Inventory Check | Display Price') 73 | .field(AssessmentIndicatorDefinition.DataType).repeat('Number', 'Decimal') 74 | .withChildren(AssessmentTaskIndDefinition.SObjectType, AssessmentTaskIndDefinition.AssessmentIndDefinitionId, 2) 75 | .junctionOf(AssessmentTaskIndDefinition.AssessmentIndDefinitionId, AssessmentTaskIndDefinition.AssessmentTaskDefinitionId) 76 | .withParents(AssessmentTaskDefinition.SObjectType, AssessmentTaskIndDefinition.AssessmentTaskDefinitionId, 1) 77 | .field(AssessmentTaskDefinition.Name).repeat('Inventory Check') 78 | .field(AssessmentTaskDefinition.TaskType).repeat('InventoryCheck') 79 | .also(3) 80 | .withParents(Product2.SObjectType, RetailStoreKpi.ProductId) 81 | .mock(); 82 | 83 | List kpis = result.get(RetailStoreKpi.SobjectType); 84 | for (RetailStoreKpi kpi : kpis) { 85 | System.debug(kpi); 86 | } 87 | 88 | Datetime endTime = Datetime.now(); 89 | System.debug(endTime.getTime() - startTime.getTime()); 90 | Database.rollback(sp); -------------------------------------------------------------------------------- /scripts/apex/demo-products.apex: -------------------------------------------------------------------------------- 1 | Savepoint sp = Database.setSavepoint(); 2 | Datetime startTime = Datetime.now(); 3 | 4 | ATK.SaveResult result = ATK.prepare(PriceBook2.SObjectType, new List{ 5 | [SELECT Id, Name FROM PriceBook2 WHERE IsStandard = TRUE][0], 6 | new PriceBook2(Name = 'Promotion Price Book', IsActive = true), 7 | new PriceBook2(Name = 'Partner Price Book', IsActive = true) 8 | }) 9 | .withChildren(PricebookEntry.SObjectType, PricebookEntry.Pricebook2Id, 60) 10 | .junctionOf(PricebookEntry.Pricebook2Id, PricebookEntry.Product2Id) 11 | .field(PricebookEntry.IsActive).repeat(true) 12 | .field(PricebookEntry.UnitPrice).repeat(9.99) 13 | .withParents(Product2.SObjectType, PricebookEntry.Product2Id, 20) 14 | .field(Product2.IsActive).repeat(true) 15 | .field(Product2.Name).index('Product {0}') 16 | .save(); 17 | 18 | Datetime endTime = Datetime.now(); 19 | System.debug(endTime.getTime() - startTime.getTime()); 20 | Database.rollback(sp); -------------------------------------------------------------------------------- /scripts/apex/demo-sales.apex: -------------------------------------------------------------------------------- 1 | // 1. Enable FLS of OpportunityId of Order 2 | 3 | Savepoint sp = Database.setSavepoint(); 4 | 5 | List priceBooks = new List{ 6 | [SELECT Id, Name FROM PriceBook2 WHERE IsStandard = TRUE][0], 7 | new PriceBook2(Name = 'Local Price Book', IsActive = true) 8 | }; 9 | 10 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 200) 11 | .field(Account.Name).index('Name-{0000}') 12 | .withChildren(Contact.SObjectType, Contact.AccountId, 400) 13 | .field(Contact.LastName).index('Name-{0000}') 14 | .field(Contact.Email).index('test.user+{0000}@email.com') 15 | .field(Contact.MobilePhone).index('+86 186 7777 {0000}') 16 | .withChildren(OpportunityContactRole.SObjectType, OpportunityContactRole.ContactId, 800) 17 | .field(OpportunityContactRole.Role).repeat('Business User', 'Decision Maker') 18 | .withParents(Opportunity.SObjectType, OpportunityContactRole.OpportunityId, 400) 19 | .field(Opportunity.Name).index('Name-{0000}') 20 | .field(Opportunity.CloseDate).addDays(Date.newInstance(2020, 1, 1), 1) 21 | .field(Opportunity.ForecastCategoryName).repeat('Pipeline') 22 | .field(Opportunity.Probability).repeat(0.9, 0.8, 0.7) 23 | .field(Opportunity.StageName).repeat('Prospecting') 24 | .field(Opportunity.TotalOpportunityQuantity).add(1000, 10) 25 | .withParents(Account.SObjectType, Opportunity.AccountId) 26 | .also(4) 27 | .withChildren(Order.SObjectType, Order.AccountId, 400) 28 | .field(Order.Name).index('Name-{0000}') 29 | .field(Order.EffectiveDate).addDays(Date.newInstance(2020, 1, 1), 1) 30 | .field(Order.Status).repeat('Draft') 31 | .withParents(Contact.SObjectType, Order.BillToContactId) 32 | .also() 33 | .withParents(Opportunity.SObjectType, Order.OpportunityId) 34 | .save(true); 35 | 36 | System.assertEquals(200, result.get(Account.SObjectType).size()); 37 | System.assertEquals(400, result.get(Contact.SObjectType).size()); 38 | System.assertEquals(800, result.get(OpportunityContactRole.SObjectType).size()); 39 | System.assertEquals(400, result.get(Opportunity.SObjectType).size()); 40 | System.assertEquals(400, result.get(Order.SObjectType).size()); 41 | 42 | for (Integer i = 0; i < 200; i++) { 43 | Account acc = (Account)result.get(Account.SObjectType)[i]; 44 | Contact contact1 = (Contact)result.get(Contact.SObjectType)[i * 2]; 45 | Contact contact2 = (Contact)result.get(Contact.SObjectType)[i * 2 + 1]; 46 | Order order1 = (Order)result.get(Order.SObjectType)[i * 2]; 47 | Order order2 = (Order)result.get(Order.SObjectType)[i * 2 + 1]; 48 | Opportunity opp1 = (Opportunity)result.get(Opportunity.SObjectType)[i * 2]; 49 | Opportunity opp2 = (Opportunity)result.get(Opportunity.SObjectType)[i * 2 + 1]; 50 | OpportunityContactRole opc1 = (OpportunityContactRole)result.get(OpportunityContactRole.SObjectType)[i * 4]; 51 | OpportunityContactRole opc2 = (OpportunityContactRole)result.get(OpportunityContactRole.SObjectType)[i * 4 + 1]; 52 | OpportunityContactRole opc3 = (OpportunityContactRole)result.get(OpportunityContactRole.SObjectType)[i * 4 + 2]; 53 | OpportunityContactRole opc4 = (OpportunityContactRole)result.get(OpportunityContactRole.SObjectType)[i * 4 + 3]; 54 | 55 | System.assertEquals(acc, contact1.Account); 56 | System.assertEquals(acc, contact2.Account); 57 | System.assertEquals(acc, opp1.Account); 58 | System.assertEquals(acc, opp2.Account); 59 | 60 | System.assertEquals(acc, order1.Account); 61 | System.assertEquals(acc, order2.Account); 62 | System.assertEquals(contact1, order1.BillToContact); 63 | System.assertEquals(contact2, order2.BillToContact); 64 | System.assertEquals(opp1, order1.Opportunity); 65 | System.assertEquals(opp2, order2.Opportunity); 66 | 67 | System.assertEquals(opp1, opc1.Opportunity); 68 | System.assertEquals(opp1, opc2.Opportunity); 69 | System.assertEquals(opp2, opc3.Opportunity); 70 | System.assertEquals(opp2, opc4.Opportunity); 71 | System.assertEquals(contact1, opc1.Contact); 72 | System.assertEquals(contact1, opc2.Contact); 73 | System.assertEquals(contact2, opc3.Contact); 74 | System.assertEquals(contact2, opc4.Contact); 75 | } 76 | 77 | Database.rollback(sp); -------------------------------------------------------------------------------- /scripts/apex/demo-users.apex: -------------------------------------------------------------------------------- 1 | { 2 | // 1. Setup > Digital Experiences > Settings 3 | // 2. Select Allow using standard external profiles for self-registration, user creation, and login. 4 | // 3. Portal Account Owener must have a role 5 | Savepoint sp = Database.setSavepoint(); 6 | 7 | ATK.SaveResult result = ATK.prepare(Account.SObjectType, 1) 8 | .field(Account.Name).index('Name-{000}') 9 | .withChildren(Contact.SObjectType, Contact.AccountId, 3) 10 | .field(Contact.LastName).index('Name-{000}') 11 | .withChildren(User.SObjectType, User.ContactId, 3) 12 | .profile('Customer Community User') 13 | .field(User.FirstName).repeat('FirstName') 14 | .field(User.LastName).repeat('LastName') 15 | .field(User.Email).index('test.user+{0000}@email.com') 16 | .field(User.UserName).index('test.user+{0000}@email.com') 17 | .field(User.Alias).index('test{0000}') 18 | .field(User.EmailEncodingKey).repeat('UTF-8') 19 | .field(User.LanguageLocaleKey).repeat('en_US') 20 | .field(User.LocaleSidKey).repeat('en_US') 21 | .field(User.TimeZoneSidKey).repeat('Pacific/Auckland') 22 | .mock(); 23 | 24 | for (Integer i = 0; i < 3; i++ ) { 25 | User user = (User)result.get(User.SObjectType)[i]; 26 | Contact contact = (Contact)result.get(Contact.SObjectType)[i]; 27 | 28 | System.assertEquals(contact, user.Contact); 29 | } 30 | 31 | Database.rollback(sp); 32 | } 33 | 34 | { 35 | Savepoint sp = Database.setSavepoint(); 36 | 37 | ATK.SaveResult result = ATK.prepare(User.SObjectType, 10) 38 | .permissionSet('Survey Creator') 39 | .profile('Chatter Free User') 40 | .field(User.FirstName).repeat('FirstName') 41 | .field(User.LastName).repeat('LastName') 42 | .field(User.Email).index('test.user+{0000}@email.com') 43 | .field(User.UserName).index('test.user+{0000}@email.com') 44 | .field(User.Alias).index('test{0000}') 45 | .field(User.EmailEncodingKey).repeat('UTF-8') 46 | .field(User.LanguageLocaleKey).repeat('en_US') 47 | .field(User.LocaleSidKey).repeat('en_US') 48 | .field(User.TimeZoneSidKey).repeat('Pacific/Auckland') 49 | .mock(); 50 | 51 | Database.rollback(sp); 52 | } 53 | -------------------------------------------------------------------------------- /scripts/shell/sfdx.sh: -------------------------------------------------------------------------------- 1 | sfdx force:source:push 2 | sfdx force:package:version:create -p ApexTestKit -x -c --wait 10 --codecoverage 3 | sfdx force:package:version:list 4 | sfdx force:package:version:promote -p 04t2v000007Cg6NAAS 5 | sfdx force:package:version:report -p 04t2v000007Cg6NAAS -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "apex-test-kit", 5 | "package": "ApexTestKit", 6 | "versionName": "ver 4.1.0", 7 | "versionNumber": "4.1.0.NEXT", 8 | "default": true 9 | } 10 | ], 11 | "name": "ApexTestKit", 12 | "namespace": "", 13 | "sfdcLoginUrl": "https://login.salesforce.com", 14 | "sourceApiVersion": "57.0", 15 | "packageAliases": { 16 | "ApexTestKit": "0Ho2v00000000FfCAI", 17 | "ApexTestKit@3.0.0-2": "04t2v000007X2zHAAS", 18 | "ApexTestKit@3.0.1-1": "04t2v000007X3BAAA0", 19 | "ApexTestKit@3.0.2-1": "04t2v000007X3LqAAK", 20 | "ApexTestKit@3.1.0-1": "04t2v000007X3Q7AAK", 21 | "ApexTestKit@3.2.0-1": "04t2v000007X4XzAAK", 22 | "ApexTestKit@3.2.1-2": "04t2v000007X4qeAAC", 23 | "ApexTestKit@3.3.1-1": "04t2v000007GQuwAAG", 24 | "ApexTestKit@3.4.0-1": "04t2v0000079BS0AAM", 25 | "ApexTestKit@3.4.1-1": "04t2v0000079BSFAA2", 26 | "ApexTestKit@3.4.1-2": "04t2v0000079BfuAAE", 27 | "ApexTestKit@3.4.2-1": "04t2v0000079BfzAAE", 28 | "ApexTestKit@3.4.3-1": "04t2v0000079Bg9AAE", 29 | "ApexTestKit@3.5.0-1": "04t2v000007GTLoAAO", 30 | "ApexTestKit@3.5.1-1": "04t2v000007GTQfAAO", 31 | "ApexTestKit@3.5.2-1": "04t2v000007GTRTAA4", 32 | "ApexTestKit@3.5.3-1": "04t2v000007GTZOAA4", 33 | "ApexTestKit@4.0.0-1": "04t2v000007GUCxAAO", 34 | "ApexTestKit@4.0.0-2": "04t2v000007GULRAA4", 35 | "ApexTestKit@4.0.0-3": "04t2v000007GULWAA4", 36 | "ApexTestKit@4.0.1-1": "04t2v000007GULbAAO", 37 | "ApexTestKit@4.1.0-1": "04t2v000007Cg6NAAS" 38 | } 39 | } --------------------------------------------------------------------------------