{"id":18949973,"url":"https://github.com/salesforce/apex-mockery","last_synced_at":"2026-02-12T14:32:45.299Z","repository":{"id":45784914,"uuid":"495337840","full_name":"salesforce/apex-mockery","owner":"salesforce","description":"Lightweight mocking library in Apex","archived":false,"fork":false,"pushed_at":"2025-07-22T14:17:16.000Z","size":869,"stargazers_count":145,"open_issues_count":5,"forks_count":21,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-08-20T11:50:07.320Z","etag":null,"topics":["apex","salesforce","unit-test"],"latest_commit_sha":null,"homepage":"","language":"Apex","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salesforce.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-23T09:16:59.000Z","updated_at":"2025-08-14T14:25:58.000Z","dependencies_parsed_at":"2024-04-23T10:43:38.859Z","dependency_job_id":"51cff5fc-559c-4bce-88fc-caccf034b465","html_url":"https://github.com/salesforce/apex-mockery","commit_stats":{"total_commits":44,"total_committers":3,"mean_commits":"14.666666666666666","dds":"0.15909090909090906","last_synced_commit":"6d7ac569b56852a72c4ec6f1f8794915482add3a"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/salesforce/apex-mockery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fapex-mockery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fapex-mockery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fapex-mockery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fapex-mockery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salesforce","download_url":"https://codeload.github.com/salesforce/apex-mockery/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fapex-mockery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29368689,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T08:51:36.827Z","status":"ssl_error","status_checked_at":"2026-02-12T08:51:26.849Z","response_time":55,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apex","salesforce","unit-test"],"created_at":"2024-11-08T13:20:05.746Z","updated_at":"2026-02-12T14:32:45.282Z","avatar_url":"https://github.com/salesforce.png","language":"Apex","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![main](https://github.com/salesforce/apex-mockery/workflows/main/badge.svg)](https://github.com/salesforce/apex-mockery/actions?query=workflow%3Amain) [![release](https://github.com/salesforce/apex-mockery/workflows/release/badge.svg)](https://github.com/salesforce/apex-mockery/actions?query=workflow%3Arelease)\n\n\u003cdiv align=\"center\"\u003e\n\t\u003cimg src=\"resources/logo.png\" width=\"256\"\u003e\n\t\u003ch1\u003eLightweight mocking library in Apex\u003c/h1\u003e  \n\u003c/div\u003e\n\nThis project provide a simple, lightweight, easy to read, fully tested mocking library for apex built using the [Apex Stub API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm).\nWe want its usage to be simple, its maintainability to be easy and to provide the best developer experience possible\n\n\u003c!-- TABLE OF CONTENTS --\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eTable of Contents\u003c/summary\u003e\n\n- [Principles](#principles)\n  - [Why you should use the library](#why-you-should-use-the-library)\n- [Installation](#installation)\n  - [Namespaced Org /!\\\\](#namespaced-org-)\n- [Usage](#usage)\n  - [Mock](#mock)\n    - [How to stub namespaced type?](#how-to-stub-namespaced-type)\n  - [Stub](#stub)\n  - [Spy](#spy)\n    - [How to Configure a spy](#how-to-configure-a-spy)\n      - [Default behaviour](#default-behaviour)\n      - [Global returns](#global-returns)\n      - [Global returns once](#global-returns-once)\n      - [Global throws](#global-throws)\n      - [Global throws once](#global-throws-once)\n      - [Global mock implementation](#global-mock-implementation)\n      - [Global mock implementation once](#global-mock-implementation-once)\n      - [Parameterized configuration](#parameterized-configuration)\n      - [Configuration order matters !](#configuration-order-matters-)\n  - [Assert on a spy](#assert-on-a-spy)\n  - [Arguments](#arguments)\n  - [Argument matcher](#argument-matcher)\n    - [Any](#any)\n    - [Equal](#equal)\n    - [jsonEqual](#jsonequal)\n    - [ofType](#oftype)\n    - [BYOM (Build your own matcher)](#byom-build-your-own-matcher)\n  - [Recipes](#recipes)\n    - [Mocking](#mocking)\n    - [Asserting](#asserting)\n- [Library architecture](#library-architecture)\n- [How to migrate my codebase?](#how-to-migrate-my-codebase)\n  - [When implementing new feature](#when-implementing-new-feature)\n  - [When touching existing/legacy code](#when-touching-existinglegacy-code)\n  - [When refactoring](#when-refactoring)\n- [Authors](#authors)\n- [Contributing](#contributing)\n- [License](#license)\n\u003c/details\u003e\n\n## Principles\n\nAPIs design come from our experience with Mockito, chai.js, sinon.js and jest.\nThe library aims to provide developers a simple way to stub, mock, spy and assert their implementations.\nDependency Injection and Inversion Of Control are key architectural concepts the system under test should implements\n\n### Why you should use the library\n\nIt helps you isolate the code from its dependency in unit test.\nUsing the library to mock your classes dependencies will contribute to improve code quality and maintanibility of your project.\n\nIt helps you write unit test by driving the behavior of the class dependencies (instead of relying on it by integration).\nUsing the library to mock DML and SOQL from your tests will help you save a lot of time in apex test execution (the tests will not sollicitate the database anymore).\n\n## Installation\n\nDeploy via the deploy button\n\n\u003ca href=\"https://githubsfdeploy.herokuapp.com?owner=salesforce\u0026repo=apex-mockery\u0026ref=main\"\u003e\n  \u003cimg alt=\"Deploy to Salesforce\"\n       src=\"https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png\"\u003e\n\u003c/a\u003e\n\nOr copy `force-app/src/classes` apex classes in your sfdx project to deploy it with your favourite deployment methods\n\nOr you can install the library using our unlocked package without namespace from the [latest release](https://github.com/salesforce/apex-mockery/releases/latest)\n\n### Namespaced Org /!\\\n\nIt's [not possible](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_unlocked_pkg_namespace_collisions.htm#:~:text=You%20can%E2%80%99t%20install%20a%20no%2Dnamespace%20unlocked%20package%20in%20an%20org%20with%20a%20namespace.) to install a non namespaced unlocked package into a namespaced org.\n\nIn this case you have those choices:\n\n- Install from sources (with or without manually prefixing classes)\n- Create your own unlocked/2GP package with your namespace containing the sources\n\nIt's [not recommended](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp_dependency_overview.htm#:~:text=Can%20a%20managed%202GP%20package%20depend%20on%20an,on%20a%20managed%201GP%20or%20managed%202GP%20package.) for a 2GP package to depends on an unlocked package, namespaced or not (ISV scenario).\n\n## Usage\n\n### Mock\n\nTo mock an instance, use the `Mock.forType` method\nIt returns a Mock instance containing the stub and all the mechanism to spy/configure/assert\n\n```java\nMock myMock = Mock.forType(MyType.class);\n```\n\n#### How to stub namespaced type?\n\nBecause `Test.createStub()` call [cannot cross namespace](\u003chttps://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm#:~:text=The%20object%20being%20mocked%20must%20be%20in%20the%20same%20namespace%20as%20the%20call%20to%20the%20Test.createStub()%20method.%20However%2C%20the%20implementation%20of%20the%20StubProvider%20interface%20can%20be%20in%20another%20namespace.\u003e), we provide a `StubBuilder` interface to stub type from your namespace.\nCreate a `StubBuilder` implementation in your namespace (it must be the same implementation as the `Mock.DefaultStubBuilder` implementation but has to be in your namespace to build type from your namespace).\n\n```java\nMock myMock = Mock.forType(MyType.class, new MyNamespace.MyStubBuilder());\n```\n\n### Stub\n\nUse the `stub` attribute to access the stub,\n\n```java\nMyType myTypeStub = (MyType) myMock.stub;\nMyService myServiceInstance = new MyServiceImpl(myTypeStub);\n```\n\n### Spy\n\nUse the `spyOn` method from the mock to spy on a method,\nIt returns a `MethodSpy` instance containing all the tools to drive its behaviour and spy on it\n\n```java\nMethodSpy myMethodSpy = myMock.spyOn('myMethod');\n```\n\n#### How to Configure a spy\n\n##### Default behaviour\n\nBy default, a spy return `null` when called, whatever the parameters received.\n\n```java\n// Act\nObject result = myTypeStub.myMethod();\n// Assert\nAssert.areEqual(null, result);\n```\n\nHave a look at the [NoConfiguration recipe](force-app/recipes/classes/mocking/NoConfiguration.cls)\n\n##### Global returns\n\nConfigure it to return a specific value, whatever the parameter received\nThe stub will always return the configured value\n\n```java\n// Arrange\nmyMethodSpy.returns(new Account(Name='Test'));\n// Act\nObject result = myTypeStub.myMethod();\n// Assert\nAssert.areEqual(new Account(Name='Test'), result);\n```\n\nHave a look at the [Returns recipe](force-app/recipes/classes/mocking/Returns.cls)\n\nYou can also configure it to return the same object multipe times\n\n```java\n// Arrange\nmyMethodSpy.returns(new Account(Name='Test'), 3);\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  // Act\n  Object result = myTypeStub.myMethod();\n  // Assert\n  Assert.areEqual(new Account(Name='Test'), result);\n}\n```\n\n##### Global returns once\n\nConfigure it to return a specific value once, whatever the parameter received\nThe stub will return the configured value once\n\n```java\n// Arrange\nmyMethodSpy.returnsOnce(new Account(Name='Test'));\n// Act\nObject result = myTypeStub.myMethod();\n// Assert\nAssert.areEqual(new Account(Name='Test'), result);\n```\n\nHave a look at the [ReturnsOnce recipe](force-app/recipes/classes/mocking/ReturnsOnce.cls)\n\n##### Global throws\n\nConfigure it to throw a specific exception, whatever the parameter received\nThe stub will always throw the configured exception\n\n```java\n// Arrange\nmyMethodSpy.throwsException(new MyException());\ntry {\n    // Act\n    Object result = myTypeStub.myMethod();\n\n    // Assert\n    Assert.fail('Expected exception was not thrown');\n} catch (Exception ex) {\n    Assert.isInstanceOfType(ex, MyException.class);\n}\n```\n\nHave a look at the [Throws recipe](force-app/recipes/classes/mocking/Throws.cls)\n\nYou can also configure it to throw the same exception multipe times\n\n```java\n// Arrange\nmyMethodSpy.throwsException(new MyException(), 3);\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  try {\n      // Act\n      Object result = myTypeStub.myMethod();\n\n      // Assert\n      Assert.fail('Expected exception was not thrown');\n  } catch (Exception ex) {\n      Assert.isInstanceOfType(ex, MyException.class);\n  }\n}\n```\n\n##### Global throws once\n\nConfigure it to throw a specific exception once, whatever the parameter received\nThe stub will throw the configured exception once\n\n```java\n// Arrange\nmyMethodSpy.throwsExceptionOnce(new MyException());\ntry {\n    // Act\n    Object result = myTypeStub.myMethod();\n\n    // Assert\n    Assert.fail('Expected exception was not thrown');\n} catch (Exception ex) {\n    Assert.isInstanceOfType(ex, MyException.class);\n}\n```\n\nHave a look at the [ThrowsOnce recipe](force-app/recipes/classes/mocking/ThrowsOnce.cls)\n\n##### Global mock implementation\n\nConfigure it to behave specifically, using the parameters or not\nThe stub will always execute the configured behavior\n\n```java\nclass MockImpl implements MethodSpy.SpyBehavior {\n  public Object execute(final List\u003cObject\u003e params) {\n    // Do something with params\n    return 'something';\n  }\n}\n\n// Arrange\nmyMethodSpy.behaves(new MockImpl());\n// Act\nObject result = myTypeStub.myMethod();\n// Assert\nAssert.areEqual('something', result);\n```\n\nHave a look at the [Returns recipe](force-app/recipes/classes/mocking/Behave.cls)\n\nYou can also configure it to execute the configured behavior multipe times\n\n```java\n// Arrange\nmyMethodSpy.behaves(Mocnew MockImpl()kImpl, 3);\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  // Act\n  Object result = myTypeStub.myMethod();\n  // Assert\n  Assert.areEqual('something', result);\n}\n```\n\n##### Global mock implementation once\n\nConfigure it to behave specifically once, using the parameters or not\nThe stub will execute the configured behavior once\n\n```java\nclass MockImpl implements MethodSpy.SpyBehavior {\n  public Object execute(final List\u003cObject\u003e params) {\n    // Do something with params\n    return 'something';\n  }\n}\n\n// Arrange\nmyMethodSpy.behaves(new MockImpl());\n// Act\nObject result = myTypeStub.myMethod();\n// Assert\nAssert.areEqual('something', result);\n```\n\nHave a look at the [ReturnsOnce recipe](force-app/recipes/classes/mocking/BehaveOnce.cls)\n\n##### Parameterized configuration\n\nConfigure it to return a specific value, when call with specific parameters\nConfigure it to return a specific value once, when call with specific parameters\nConfigure it to return a specific value times N, when call with specific parameters\nConfigure it to throw a specific value, when call with specific parameters\nConfigure it to throw a specific value once, when call with specific parameters\nConfigure it to throw a specific value times N, when call with specific parameters\nConfigure it to with a specific implementation, when call with specific parameters\nConfigure it to with a specific implementation once, when call with specific parameters\nConfigure it to with a specific implementation times N, when call with specific parameters\n\n```java\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), 10)\n    .thenReturn(new Account(Name='Test'));\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), 10)\n    .thenReturnOnce(new Account(Name='Test Once'));\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), 10)\n    .thenReturn(new Account(Name='Test Times'), 3);\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenThrow(new MyException);\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenThrowOnce(new MyOtherException());\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenThrow(new MyException(), 3);\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenBehave(new MockImpl());\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenBehaveOnce(new MockImpl());\n\n// Arrange\nmyMethodSpy\n    .whenCalledWith(Argument.any(), -1)\n    .thenBehave(new MockImpl(), 3);\n\n\n// Act\nObject result = myTypeStub.myMethod('nothing', 10);\n\n// Assert\nAssert.areEqual(new Account(Name='Test Once'), result);\n\n// Act\nObject result = myTypeStub.myMethod('nothing', 10);\n\n// Assert\nAssert.areEqual(new Account(Name='Test'), result);\n\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  // Act\n  Object result = myTypeStub.myMethod('nothing', 10);\n\n  // Assert\n  Assert.areEqual(new Account(Name='Test Times'), result);\n}\n\n// Act\ntry {\n    myTypeStub.myMethod('value', -1);\n\n    // Assert\n    Assert.fail('Expected exception was not thrown');\n} catch (Exception ex) {\n    Assert.isInstanceOfType(ex, MyOtherException.class);\n}\n\n// Act\ntry {\n    myTypeStub.myMethod('value', -1);\n\n    // Assert\n    Assert.fail('Expected exception was not thrown');\n} catch (Exception ex) {\n    Assert.isInstanceOfType(ex, MyException.class);\n}\n\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  // Act\n  try {\n      myTypeStub.myMethod('value', -1);\n\n      // Assert\n      Assert.fail('Expected exception was not thrown');\n  } catch (Exception ex) {\n      Assert.isInstanceOfType(ex, MyException.class);\n  }\n}\n\n// Act\nObject result = myTypeStub.myMethod('nothing', 10);\n\n// Assert\nAssert.areEqual('Custom Behavior', result);\n\n// Act\nObject result = myTypeStub.myMethod('nothing', 10);\n\n// Assert\nAssert.areEqual('Custom Behavior', result);\n\nfor(Integer i = 0 ; i \u003c 3 ; ++i) {\n  // Act\n  Object result = myTypeStub.myMethod('nothing', 10);\n\n  // Assert\n  Assert.areEqual('Custom Behavior', result);\n}\n```\n\nHave a look at the [mocking recipes](force-app/recipes/classes/mocking/) to have a deeper overview of what you can do with the mocking API.\n\n##### Configuration order matters !\n\n**TL;DR**\n\nThe order of the spy configuration drive how it will behave.\n\n1. If no configuration at all, then return null (default behavior).\n1. Then, it checks the \"matching\" `whenCalledWith` `once` and `times` configurations and apply them in setup order.\n1. Then, it checks the \"global once\" and \"global times\" (`returnsOnce` or `throwsExceptionOnce` or `returns(object, integer)` or `throws(exception, integer)`) configuration and apply them in setup order.\n1. Then, it checks the \"matching\" `whenCalledWith` configurations and apply them in setup order.\n1. Then, it checks the \"global\" (`returns` or `throwsException`) configurations and apply them in setup order.\n\nIf there is a configuration and it does not match then it throws a `ConfigurationException`.\nThe error message will contains the arguments and the configuration.\nUse it to help you understand the root cause of the issue (configuration/regression/you name it).\n\nThe order of the global configuration matters.\nIf global throw is setup after global returns then `throwException` will apply.\n\n```java\nmyMethodSpy.returns(new Account(Name='Test'));\nmyMethodSpy.throwsException(new MyException());\nObject result = myTypeStub.myMethod(); // throws\n```\n\nIf global returns is setup after global throw then `returns` will apply\n\n```java\nmyMethodSpy.throwsException(new MyException());\nmyMethodSpy.returns(new Account(Name='Test'));\nObject result = myTypeStub.myMethod(); // return configured value\n```\n\nFor global configuration, the last configured will apply.\nSame as if you would have configured the spy twice to return (or throw), the last global configuration would be the one kept.\n\n### Assert on a spy\n\nUse the `Expect` class to assert on a spy\nIt exposes the method `that` and returns a `MethodSpyExpectable` type.\nUse the convenient assertion methods the following way:\n\n```java\n// hasNotBeenCalled\nExpect.that(myMethodSpy).hasNotBeenCalled();\n\n// hasBeenCalled\nExpect.that(myMethodSpy).hasBeenCalled();\n\n// hasBeenCalledTimes\nExpect.that(myMethodSpy).hasBeenCalledTimes(2);\n\n// hasBeenCalledWith\nExpect.that(myMethodSpy).hasBeenCalledWith('stringValue', Argument.any(), true, ...); // up to 5 parameters\nExpect.that(myMethodSpy).hasBeenCalledWith(Argument.ofList(new List\u003cObject\u003e{Argument.any(), Argument.any(), ... })); // for more than 5 parameters\n\n// hasBeenLastCalledWith\nExpect.that(myMethodSpy).hasBeenLastCalledWith('stringValue', Argument.any(), true, ...); // up to 5 parameters\nExpect.that(myMethodSpy).hasBeenLastCalledWith(Argument.ofList(new List\u003cObject\u003e{Argument.any(), Argument.any(), ... })); // for more than 5 parameters\n```\n\nHave a look at the [assertions recipes](force-app/recipes/classes/asserting/) to have a deeper overview of what you can do with the assertion API\n\n### Arguments\n\nConfiguring a stub (`spy.whenCalledWith(...)`) and asserting (`Expect.that(myMethodSpy).hasBeenCalledWith` and `Expect.that(myMethodSpy).hasBeenLastCalledWith`) a stub uses `Argument.Matchable` interface.\n\nYou can either use raw values with notation like `spy.whenCallWith('value1', false, ...)`or `hasBeenCalledWith(param1, param2, ...)` up to 5 arguments.\n\nIt wrapes value with a `Argument.equals` when called with any kind of parameter.\n\nWhen called with a `Argument.Matchable` type, it considers it as a parameter, use it directly without wrapping it with a `Argument.equals`.\n\nIf you need more arguments in your method calls, `Argument` offers the `ofList` API to create parameters for that, so that you can do `spy.whenCallWith(Argument.ofList(new List\u003cObject\u003e{...})))`or `hasBeenCalledWith(Argument.ofList(new List\u003cObject\u003e{...}))))`\n\n```java\nList\u003cArgument.Matchable\u003e emptyParameters = Argument.empty();\n\nList\u003cArgument.Matchable\u003e myMethodParameters = Argument.of(10, 'string'); // Up to five\n\nList\u003cArgument.Matchable\u003e myMethodWithLongParameters = Argument.ofList(new List\u003cObject\u003e{10, 'string', true, 20, false, 'Sure'});\n```\n\n### Argument matcher\n\nThe library provide OOTB (out of the box) Matchables ready for use and fully tested.\nThe library accept your own matchers for specific use cases and reusability.\n\n#### Any\n\n`Argument.any()` matches anything\n\n```java\nArgument.any();\n```\n\n#### Equal\n\n`Argument.equals()` (the default) matches with native deep equals\n\n```java\nArgument.equals(10);\n```\n\n#### jsonEqual\n\n`Argument.jsonEquals(new WithoutEqualsType())` matches with json string equals. Convenient to match without `equals` type\n\n```java\nArgument.jsonEquals(new WithoutEqualsType(10, true, '...'));\n```\n\nNamespaced custom types must add the `@JsonAccess` [annotation](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_JsonAccess.htm) with `serializable='always' to the class when using the unlocked package version.\n\n#### ofType\n\n`Argument.ofType()` matches on the parameter type\n\n```java\n// To match any Integer\nArgument.ofType('Integer');\n// To match any Account SObject\nArgument.ofType(Account.getSObjectType());\n// To match any CustomType class instance\nArgument.ofType(CustomType.class);\n```\n\n#### BYOM (Build your own matcher)\n\nUse the `Argument.Matchable` interface and then use it with `Argument` APIs\n\n```java\n@IsTest\npublic class MyMatchable implements Argument.Matchable {\n  public Boolean matches(Object callArgument) {\n    boolean matches = false;\n\n    // custom logic to determine if it matches here\n    ...\n\n    return matches;\n  }\n\n  public Boolean equals(Object obj) {\n      if (obj == null || !(obj instanceof MyMatchable)) {\n        return false;\n      }\n      MyMatchable other = (MyMatchable) obj;\n      return \u003cthis fields custom comparison with other instance\u003e;\n    }\n}\n\nList\u003cArgument.Matchable\u003e args = Argument.of(new MyMatchable(), ...otherArguments);\n```\n\nImplements the `public Boolean equals(Object obj)` method on your custom matchable so we can compare list of arguments\n\nHave a look at the [overview recipes](force-app/recipes/classes/ApexMockeryOverview.cls) to have a deeper overview of what you can do with the library\n\n### Recipes\n\nThey have their own [folder](force-app/recipes/).\nIt contains usage example for `mocking` and `asserting`\nIt contains one classe for each use cases the library covers\n\n#### Mocking\n\n- [No Configuration](force-app/recipes/classes/mocking/NoConfiguration.cls): spy not configured\n- [Returns](force-app/recipes/classes/mocking/Returns.cls): spy configured to return\n- [Returns](force-app/recipes/classes/mocking/ReturnsOnce.cls): spy configured to return once\n- [ReturnsThenThrows](force-app/recipes/classes/mocking/ReturnsThenThrows.cls): spy configured to throw\n- [Throws](force-app/recipes/classes/mocking/Throws.cls): spy configured to throw\n- [ThrowsOnce](force-app/recipes/classes/mocking/ThrowsOnce.cls): spy configured to throw once\n- [ThrowsThenReturns](force-app/recipes/classes/mocking/ThrowsThenReturns.cls): spy configured to return\n- [WhenCalledWithCustomMatchable_ThenReturn](force-app/recipes/classes/mocking/WhenCalledWithCustomMatchable_ThenReturn.cls): spy configured with custom matcher to return\n- [WhenCalledWithEqualMatching_ThenReturn](force-app/recipes/classes/mocking/WhenCalledWithEqualMatching_ThenReturn.cls): spy configured with equals matcher to return\n- [WhenCalledWithJSONMatching_ThenReturn](force-app/recipes/classes/mocking/WhenCalledWithJSONMatching_ThenReturn.cls): spy configured with JSON matcher to return\n- [WhenCalledWithMatchingThrowsAndReturns](force-app/recipes/classes/mocking/WhenCalledWithMatchingThrowsAndReturns.cls): spy configured with matcher to return and to throw\n- [WhenCalledWithNotMatchingAndReturn](force-app/recipes/classes/mocking/WhenCalledWithNotMatchingAndReturn.cls): spy configured with matcher and global return, called without matching parameters\n- [WhenCalledWithTypeMatching_ThenReturn](force-app/recipes/classes/mocking/WhenCalledWithTypeMatching_ThenReturn.cls): spy configured with type matcher to return\n- [WhenCalledWith_ThenReturnOnce](force-app/recipes/classes/mocking/WhenCalledWith_ThenReturnOnce.cls): spy configured with a matcher to return once\n- [WhenCalledWith_ThenThrow](force-app/recipes/classes/mocking/WhenCalledWith_ThenThrow.cls): spy configured with a matcher to throw\n- [WhenCalledWith_ThenThrowOnce](force-app/recipes/classes/mocking/WhenCalledWith_ThenThrowOnce.cls): spy configured with a matcher to throw once\n- [WhenCalledWithoutMatchingConfiguration](force-app/recipes/classes/mocking/WhenCalledWithoutMatchingConfiguration.cls): spy configured and called without matching parameters\n\n#### Asserting\n\n- [HasBeenCalled](force-app/recipes/classes/asserting/HasBeenCalled.cls): spy called\n- [HasBeenCalledTimes](force-app/recipes/classes/asserting/HasBeenCalledTimes.cls): spy called times\n- [HasBeenCalledWith](force-app/recipes/classes/asserting/HasBeenCalledWith.cls): spy called with equal matcher\n- [HasBeenCalledWithCustomMatchable](force-app/recipes/classes/asserting/HasBeenCalledWithCustomMatchable.cls): spy called with custom matcher\n- [HasBeenCalledWithJSONMatchable](force-app/recipes/classes/asserting/HasBeenCalledWithJSONMatchable.cls): spy called with JSON matcher\n- [HasBeenCalledWithTypeMatchable](force-app/recipes/classes/asserting/HasBeenCalledWithTypeMatchable.cls): spy called with type matcher\n- [HasBeenLastCalledWith](force-app/recipes/classes/asserting/HasBeenLastCalledWith.cls): spy last called with equal matcher\n- [HasNotBeenCalled](force-app/recipes/classes/asserting/HasNotBeenCalled.cls): spy not called\n\n## Library architecture\n\nThe library repository has 3 parts:\n\n- Test classes in the `force-app/src` folder are what you need to use the lib, no more. Installation button deploy this folder.\n- Test classes in the `force-app/test` folder are what we need to maintain the library and is not required in production.\n- Test classes in the `force-app/recipes` folder are what you can use to have a deeper understanding of the library usages.\n\n![apex mockery class diagram](resources/class_diagram.png)\n\n## How to migrate my codebase?\n\nConsidering the concept of test pyramid, the importance of unit test in term of maintainability, how it impacts positively the deployment speed and how the lib can help you doing that, how do I migrate my entire code base to have a well balanced test pyramid ?\n\nHere are the way we suggest to follow to enforce proper unit test\n\n### When implementing new feature\n\nLook for unit test changes at the PR level, ensure the unit test is well decoupled from its dependencies\n\n- Ensure the code base stays clean\n- Use the code review stage as an enablement tool for the team\n\n### When touching existing/legacy code\n\nDecouple production code using dependency injection\nThen rewrite unit tests\n\n- Speed up test execution of this area of the code\n- Improve maintainability and Developer Experience\n\n### When refactoring\n\nDecouple production code using dependency injection\nThen write unit tests\nThen you can either shrink or delete old integrated test\n\n- The apex class under test becomes decoupled from its dependencies\n- First step towards SOLID design, you’ll be able to segregate responsibility further\n\n## Authors\n\n- [Antoine Rosenbach](https://github.com/arosenbach) _Initial contributor_\n- [Lionel Armanet](https://github.com/lionelarmanet) _Initial contributor_\n- [Ludovic Meurillon](https://github.com/LudoMeurillon) _Initial contributor_\n- [Sebastien Colladon](https://github.com/scolladon-sfdc)\n\n## Contributing\n\nAny contributions you make are **appreciated**.\n\nSee [contributing.md](/CONTRIBUTING.md) for apex-mockery contribution principles.\n\n## License\n\nThis project license is BSD 3 - see the [LICENSE.md](LICENSE.md) file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce%2Fapex-mockery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalesforce%2Fapex-mockery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce%2Fapex-mockery/lists"}