├── .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 |   
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 |
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 |
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 | }
--------------------------------------------------------------------------------