====== Mocking ====== Mocking can be very useful for unit testing. As the name says we're trying to test small units of code, not the whole thing, which is why it is useful to "mock" (nachahmen/imitieren german) parts of the application (or not the application) to be able to focus the test on the actual unit. Its also useful for foreign APIs to be able to not actually call the real API, especially useful if API is rate limited. ===== Mocking Code ===== Sometimes mocking framework logic can be useful for testing things like [[..:..:cakephp:traits|traits]]. Of course you could just implicitly test the trait by implementing it in a different class and then testing that class' functionality. But we like to explicitly test most if not all units. For tesing traits you can basically mock all the data you give the trait function and mock the thing that functions will be called upon, like a table class. You can then expect certain framework functions to be called (or your own functions work too). Including which parameters are given and what the function is supposed to return. For example this whole shiblang here in CSABM: protected $subject; /** @inheritdoc */ public function setUp(): void { parent::setUp(); $this->subject = new class { use UpsertTrait; public function upsert( Table $table, Item $data, Closure $createClosure, array $patchData, ?array $search = null, ): void { $this->upsertRecord( $table, $data, $createClosure, $patchData, $search ); } }; } /** * Tests the upsertRecord method * * @param array|null $search The search condition to test with * @param array $expectedSearch The search conditions that are expected to be used * @return void * @dataProvider upsertDataProvider * @covers ::upsertRecord */ public function testUpsertRecord(?array $search, array $expectedSearch): void { $table = $this->createMock(Table::class); $createClosure = function () { }; $patchData = []; $entity = new Entity(['patched' => false]); $patchedEntity = new Entity(['patched' => true]); $table->expects(self::once()) ->method('findOrCreate') ->with($expectedSearch, $createClosure) ->willReturn($entity); $table->expects(self::once()) ->method('patchEntity') ->with($entity, $patchData) ->willReturn($patchedEntity); $table->expects(self::once()) ->method('saveOrFail') ->with($patchedEntity); $item = new class implements Item { public $uuid; public function toList(): array { return []; } }; $this->subject->upsert($table, $item, $createClosure, $patchData, $search); } /** * Data provider for upsert test cases * * @return array */ public static function upsertDataProvider(): array { return [ 'no search' => [ null, ['uuid' => null], ], 'with search' => [ ['email' => 'me@wirz.com'], ['email' => 'me@wirz.com'], ], ]; } Here we test that an entry is first found with a given search parameter and a create closure incase it is not found. Then we check that the return value is an entity, either found or created. Then we test that ''patchEntity'' is given the new entity and patchData and that it returns a different patched entity. Last we test that ''saveOrFail'' is given the new patched entity. Mocking for tests is usually alot more complex than just implicitly testing it. But it is nice to have that full test coverage to make sure no cases were forgotten about. ===== Mocking API Responses ===== Mocking API responses basically does what the title says. You use it to fake an API response, so that you don't actually have to send a real request to the API. Its very useful for rate limited APIs that you can't just request 50 times in a few seconds with tests. Its usually used to test the code that implements the API, by mocking decently realistic data from the API and asserting that the code handles the response correctly. In CSABM we use it like so: public function testGetServersDetails(): void { $mockedResponse = new Response( HttpStatuses::STATUS_OK, [], '{ "meta" : { "limit" : 20, "offset" : 0, "total_count" : 5 }, "objects" : [ { "cpu" : 4000, "mem" : 4294967296, "meta" : { "description" : "" }, "name" : "erv-snoke-upgrade", "uuid" : "3756b540-fd8b-485e-bb42-7672a8701dab" } ] }' ); $session = $this->initApiSessionWithResponse($mockedResponse); $serverDetails = new ServerDetails($session); $servers = $serverDetails->getServersDetails(); $accountId = 1; self::assertCount(1, $servers); self::assertSame($accountId, $servers->first()->accountId); self::assertSame('3756b540-fd8b-485e-bb42-7672a8701dab', $servers->first()->uuid); self::assertSame('erv-snoke-upgrade', $servers->first()->name); self::assertSame('', $servers->first()->description); self::assertSame(4000, $servers->first()->cpu); self::assertSame(4294967296, $servers->first()->ram); } We create a mocked API response with decently realistic data, that we then assert if it was correctly transferred into CloudSigma Items.