Browse Source

Add better tests for HasOne.

Use fixtures and add integration tests for the association. When we
first built the Association classes we focused on unit tests. Over time
I've found them harder to maintain and update as they are tightly
coupled to the implementation. Fixture based integration tests seem to
have aged better.
Mark Story 9 years ago
parent
commit
0fc7f57c7b
2 changed files with 177 additions and 96 deletions
  1. 57 0
      tests/Fixture/ProfilesFixture.php
  2. 120 96
      tests/TestCase/ORM/Association/HasOneTest.php

+ 57 - 0
tests/Fixture/ProfilesFixture.php

@@ -0,0 +1,57 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+/**
+ * ProfileFixture
+ */
+class ProfilesFixture extends TestFixture
+{
+
+    /**
+     * fields property
+     *
+     * @var array
+     */
+    public $fields = [
+        'id' => ['type' => 'integer'],
+        'user_id' => ['type' => 'integer', 'null' => false],
+        'first_name' => ['type' => 'string', 'null' => true],
+        'last_name' => ['type' => 'string', 'null' => true],
+        'is_active' => ['type' => 'boolean', 'null' => false, 'default' => true],
+        '_constraints' => [
+            'primary' => ['type' => 'primary', 'columns' => ['id']],
+            'user_idx' => [
+                'type' => 'foreign',
+                'columns' => ['user_id'],
+                'references' => ['users', 'id']
+            ]
+        ]
+    ];
+
+    /**
+     * records property
+     *
+     * @var array
+     */
+    public $records = [
+        ['user_id' => 1, 'first_name' => 'mariano', 'last_name' => 'iglesias', 'is_active' => false],
+        ['user_id' => 2, 'first_name' => 'nate', 'last_name' => 'abele', 'is_active' => false],
+        ['user_id' => 3, 'first_name' => 'larry', 'last_name' => 'masters', 'is_active' => true],
+        ['user_id' => 4, 'first_name' => 'garrett', 'last_name' => 'woodworth', 'is_active' => false],
+    ];
+}

+ 120 - 96
tests/TestCase/ORM/Association/HasOneTest.php

@@ -28,6 +28,13 @@ use Cake\TestSuite\TestCase;
 class HasOneTest extends TestCase
 {
     /**
+     * Fixtures to load
+     *
+     * @var array
+     */
+    public $fixtures = ['core.users', 'core.profiles'];
+
+    /**
      * Set up
      *
      * @return void
@@ -35,36 +42,8 @@ class HasOneTest extends TestCase
     public function setUp()
     {
         parent::setUp();
-        $this->user = TableRegistry::get('Users', [
-            'schema' => [
-                'id' => ['type' => 'integer'],
-                'username' => ['type' => 'string'],
-                '_constraints' => [
-                    'primary' => ['type' => 'primary', 'columns' => ['id']]
-                ]
-            ]
-        ]);
-        $this->profile = TableRegistry::get('Profiles', [
-            'schema' => [
-                'id' => ['type' => 'integer'],
-                'first_name' => ['type' => 'string'],
-                'user_id' => ['type' => 'integer'],
-                '_constraints' => [
-                    'primary' => ['type' => 'primary', 'columns' => ['id']]
-                ]
-            ]
-        ]);
-        $this->profilesTypeMap = new TypeMap([
-            'Profiles.id' => 'integer',
-            'id' => 'integer',
-            'Profiles.first_name' => 'string',
-            'first_name' => 'string',
-            'Profiles.user_id' => 'integer',
-            'user_id' => 'integer',
-            'Profiles__first_name' => 'string',
-            'Profiles__user_id' => 'integer',
-            'Profiles__id' => 'integer',
-        ]);
+        $this->user = TableRegistry::get('Users');
+        $this->profile = TableRegistry::get('Profiles');
     }
 
     /**
@@ -112,40 +91,21 @@ class HasOneTest extends TestCase
      */
     public function testAttachTo()
     {
-        $query = $this->getMockBuilder('\Cake\ORM\Query')
-            ->setMethods(['join', 'select'])
-            ->setConstructorArgs([null, null])
-            ->getMock();
         $config = [
             'foreignKey' => 'user_id',
             'sourceTable' => $this->user,
             'targetTable' => $this->profile,
+            'property' => 'profile',
+            'joinType' => 'INNER',
             'conditions' => ['Profiles.is_active' => true]
         ];
         $association = new HasOne('Profiles', $config);
-        $field = new IdentifierExpression('Profiles.user_id');
-        $query->expects($this->once())->method('join')->with([
-            'Profiles' => [
-                'conditions' => new QueryExpression([
-                    'Profiles.is_active' => true,
-                    ['Users.id' => $field],
-                ], $this->profilesTypeMap),
-                'type' => 'LEFT',
-                'table' => 'profiles'
-            ]
-        ]);
-        $query->expects($this->once())->method('select')->with([
-            'Profiles__id' => 'Profiles.id',
-            'Profiles__first_name' => 'Profiles.first_name',
-            'Profiles__user_id' => 'Profiles.user_id'
-        ]);
+        $query = $this->user->find();
         $association->attachTo($query);
 
-        $this->assertEquals(
-            'string',
-            $query->typeMap()->type('Profiles__first_name'),
-            'Associations should map types.'
-        );
+        $results = $query->order('Users.id')->toArray();
+        $this->assertCount(1, $results, 'Only one record because of conditions & join type');
+        $this->assertSame('masters', $results[0]->Profiles['last_name']);
     }
 
     /**
@@ -155,29 +115,15 @@ class HasOneTest extends TestCase
      */
     public function testAttachToNoFields()
     {
-        $query = $this->getMockBuilder('\Cake\ORM\Query')
-            ->setMethods(['join', 'select'])
-            ->setConstructorArgs([null, null])
-            ->getMock();
         $config = [
             'sourceTable' => $this->user,
             'targetTable' => $this->profile,
             'conditions' => ['Profiles.is_active' => true]
         ];
         $association = new HasOne('Profiles', $config);
-        $field = new IdentifierExpression('Profiles.user_id');
-        $query->expects($this->once())->method('join')->with([
-            'Profiles' => [
-                'conditions' => new QueryExpression([
-                    'Profiles.is_active' => true,
-                    ['Users.id' => $field],
-                ], $this->profilesTypeMap),
-                'type' => 'LEFT',
-                'table' => 'profiles'
-            ]
-        ]);
-        $query->expects($this->never())->method('select');
+        $query = $this->user->query();
         $association->attachTo($query, ['includeFields' => false]);
+        $this->assertEmpty($query->clause('select'));
     }
 
     /**
@@ -188,18 +134,37 @@ class HasOneTest extends TestCase
      */
     public function testAttachToMultiPrimaryKey()
     {
-        $query = $this->getMockBuilder('\Cake\ORM\Query')
-            ->setMethods(['join', 'select'])
-            ->setConstructorArgs([null, null])
-            ->getMock();
+        $selectTypeMap = new TypeMap([
+            'Profiles.id' => 'integer',
+            'id' => 'integer',
+            'Profiles.first_name' => 'string',
+            'first_name' => 'string',
+            'Profiles.user_id' => 'integer',
+            'user_id' => 'integer',
+            'Profiles__first_name' => 'string',
+            'Profiles__user_id' => 'integer',
+            'Profiles__id' => 'integer',
+            'Profiles__last_name' => 'string',
+            'Profiles.last_name' => 'string',
+            'last_name' => 'string',
+            'Profiles__is_active' => 'boolean',
+            'Profiles.is_active' => 'boolean',
+            'is_active' => 'boolean',
+        ]);
         $config = [
             'sourceTable' => $this->user,
             'targetTable' => $this->profile,
             'conditions' => ['Profiles.is_active' => true],
             'foreignKey' => ['user_id', 'user_site_id']
         ];
+
         $this->user->primaryKey(['id', 'site_id']);
         $association = new HasOne('Profiles', $config);
+
+        $query = $this->getMockBuilder('\Cake\ORM\Query')
+            ->setMethods(['join', 'select'])
+            ->setConstructorArgs([null, null])
+            ->getMock();
         $field1 = new IdentifierExpression('Profiles.user_id');
         $field2 = new IdentifierExpression('Profiles.user_site_id');
         $query->expects($this->once())->method('join')->with([
@@ -207,7 +172,7 @@ class HasOneTest extends TestCase
                 'conditions' => new QueryExpression([
                     'Profiles.is_active' => true,
                     ['Users.id' => $field1, 'Users.site_id' => $field2],
-                ], $this->profilesTypeMap),
+                ], $selectTypeMap),
                 'type' => 'LEFT',
                 'table' => 'profiles'
             ]
@@ -224,7 +189,7 @@ class HasOneTest extends TestCase
      * @expectedExceptionMessage Cannot match provided foreignKey for "Profiles", got "(user_id)" but expected foreign key for "(id, site_id)"
      * @return void
      */
-    public function testAttachToMultiPrimaryKeyMistmatch()
+    public function testAttachToMultiPrimaryKeyMismatch()
     {
         $query = $this->getMockBuilder('\Cake\ORM\Query')
             ->setMethods(['join', 'select'])
@@ -289,12 +254,9 @@ class HasOneTest extends TestCase
      */
     public function testPropertyNoPlugin()
     {
-        $mock = $this->getMockBuilder('Cake\ORM\Table')
-            ->disableOriginalConstructor()
-            ->getMock();
         $config = [
             'sourceTable' => $this->user,
-            'targetTable' => $mock,
+            'targetTable' => $this->profile,
         ];
         $association = new HasOne('Contacts.Profiles', $config);
         $this->assertEquals('profile', $association->property());
@@ -308,28 +270,24 @@ class HasOneTest extends TestCase
      */
     public function testAttachToBeforeFind()
     {
-        $query = $this->getMockBuilder('\Cake\ORM\Query')
-            ->setMethods(['join', 'select'])
-            ->setConstructorArgs([null, null])
-            ->getMock();
         $config = [
             'foreignKey' => 'user_id',
             'sourceTable' => $this->user,
             'targetTable' => $this->profile,
         ];
-        $listener = $this->getMockBuilder('stdClass')
-            ->setMethods(['__invoke'])
-            ->getMock();
-        $this->profile->eventManager()->attach($listener, 'Model.beforeFind');
+        $query = $this->user->query();
+
+        $this->listenerCalled = false;
+        $this->profile->eventManager()->on('Model.beforeFind', function($event, $query, $options, $primary) {
+            $this->listenerCalled = true;
+            $this->assertInstanceOf('\Cake\Event\Event', $event);
+            $this->assertInstanceOf('\Cake\ORM\Query', $query);
+            $this->assertInstanceOf('\ArrayObject', $options);
+            $this->assertFalse($primary);
+        });
         $association = new HasOne('Profiles', $config);
-        $listener->expects($this->once())->method('__invoke')
-            ->with(
-                $this->isInstanceOf('\Cake\Event\Event'),
-                $this->isInstanceOf('\Cake\ORM\Query'),
-                $this->isInstanceOf('\ArrayObject'),
-                false
-            );
         $association->attachTo($query);
+        $this->assertTrue($this->listenerCalled, 'beforeFind event not fired.');
     }
 
     /**
@@ -366,4 +324,70 @@ class HasOneTest extends TestCase
             return $q->applyOptions(['something' => 'more']);
         }]);
     }
+
+    /**
+     * Test cascading deletes.
+     *
+     * @return void
+     */
+    public function testCascadeDelete()
+    {
+        $config = [
+            'dependent' => true,
+            'sourceTable' => $this->user,
+            'targetTable' => $this->profile,
+            'conditions' => ['Profiles.is_active' => true],
+            'cascadeCallbacks' => false,
+        ];
+        $association = new HasOne('Profiles', $config);
+
+        $this->profile->eventManager()->on('Model.beforeDelete', function () {
+            $this->fail('Callbacks should not be triggered when callbacks do not cascade.');
+        });
+
+        $entity = new Entity(['id' => 1]);
+        $association->cascadeDelete($entity);
+
+        $query = $this->profile->query()->where(['user_id' => 1]);
+        $this->assertEquals(1, $query->count(), 'Left non-matching row behind');
+
+        $query = $this->profile->query()->where(['user_id' => 3]);
+        $this->assertEquals(1, $query->count(), 'other records left behind');
+
+        $user = new Entity(['id' => 3]);
+        $this->assertTrue($association->cascadeDelete($user));
+        $query = $this->profile->query()->where(['user_id' => 3]);
+        $this->assertEquals(0, $query->count(), 'Matching record was deleted.');
+    }
+
+    /**
+     * Test cascading delete with has many.
+     *
+     * @return void
+     */
+    public function testCascadeDeleteCallbacks()
+    {
+        $config = [
+            'dependent' => true,
+            'sourceTable' => $this->user,
+            'targetTable' => $this->profile,
+            'conditions' => ['Profiles.is_active' => true],
+            'cascadeCallbacks' => true,
+        ];
+        $association = new HasOne('Profiles', $config);
+
+        $user = new Entity(['id' => 1]);
+        $this->assertTrue($association->cascadeDelete($user));
+
+        $query = $this->profile->query()->where(['user_id' => 1]);
+        $this->assertEquals(1, $query->count(), 'Left non-matching row behind');
+
+        $query = $this->profile->query()->where(['user_id' => 3]);
+        $this->assertEquals(1, $query->count(), 'other records left behind');
+
+        $user = new Entity(['id' => 3]);
+        $this->assertTrue($association->cascadeDelete($user));
+        $query = $this->profile->query()->where(['user_id' => 3]);
+        $this->assertEquals(0, $query->count(), 'Matching record was deleted.');
+    }
 }