Advertisement
gvsanz

Social API Unit Test example

Jun 22nd, 2019
322
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 7.96 KB | None | 0 0
  1. <?php
  2.  
  3. // Don't forget namespacing.
  4. namespace Drupal\Tests\social_api\Unit;
  5.  
  6. // You must ordered your imports in alphabetical order.
  7. use Drupal\Core\StringTranslation\StringTranslationTrait;
  8. use Drupal\social_api\Controller\SocialApiController;
  9. use Drupal\social_api\Plugin\NetworkManager;
  10. use Drupal\Tests\UnitTestCase;
  11. use Symfony\Component\DependencyInjection\ContainerInterface;
  12.  
  13. /**
  14.  * Write a test (meaning a method in the following class) per case.
  15.  *
  16.  * Remember that the method you're testing MUST NOT be mocked. Otherwise, why
  17.  * do you bother testing? As an example, I'll add an example for the
  18.  * integrations method in SocialApiController.
  19.  *
  20.  * Ask yourself
  21.  *
  22.  * 1) What should this method do? What am I expecting it to do?
  23.  * Answer: This method should return a Drupal rendering array that is generated
  24.  * using the array of Network plugins for the given type (social_auth or
  25.  * social_post).
  26.  *
  27.  * 2) Does this method depends on calling other methods?
  28.  * Answer: Yes, it depends on the Network plugins array returned by the method
  29.  * getDefinitions() in NetworkManager. But getDefinitions() is not the method
  30.  * we want to test, so we can mock it and assume that it's returning the value
  31.  * we expect. Again, I'm mocking helper functions/methods that are called inside
  32.  * the method I'm testing. I'm not mocking the method I want to test.
  33.  *
  34.  * 3) What are the paths I should consider?
  35.  * A path is a different place your code can go to depending on a value. It is
  36.  * more clear with an example
  37.  *
  38.  * $x = helperFunction();
  39.  * if ($x) {
  40.  *   doSomething();
  41.  *   return 'a';
  42.  * }
  43.  * else {
  44.  *   doSomethingElse();
  45.  *   return 'b';
  46.  * }
  47.  *
  48.  * I can see that there are two paths here. One of them is the path inside the
  49.  * if. The other is the one inside the else. Notice that the path your test
  50.  * takes depends on the value of $x which depends on a helper function. You
  51.  * must write a test per path. This implies that you must change the value
  52.  * returned by the mocked helperFunction() to go through the path you're
  53.  * interested in.
  54.  *
  55.  * In the specific case of SocialApi::integrations, I don't see more than one
  56.  * single path.
  57.  *
  58.  * 4) What are the possible cases?
  59.  * Even though there is one single path, there's something else you should
  60.  * notice. What you get from integrations() depends on the value you passed as
  61.  * an argument and on the value returned by getDefinitions. You must write a test
  62.  * for each case. Example:
  63.  *   * When getting no definitions at all, the render array mush look like X
  64.  *   * When getting definitions for both social_auth and social_post, but
  65.  *     my argument $type = 'social_auth', the render array must look like Y.
  66.  *
  67.  * @coversDefaultClass \Drupal\social_api\Controller\SocialApiController
  68.  * @group SocialAPIImplementer
  69.  */
  70. class SocialApiControllerTest extends UnitTestCase {
  71.  
  72.   use StringTranslationTrait;
  73.  
  74.   /**
  75.    * The tested Social API Controller.
  76.    *
  77.    * I'm making this a property because it is used all over the file.
  78.    *
  79.    * @var \Drupal\social_api\Controller\SocialApiController
  80.    */
  81.   protected $controller;
  82.  
  83.   /**
  84.    * The Social API Network Manager.
  85.    *
  86.    * @var \Drupal\social_api\Plugin\NetworkManager|\PHPUnit_Framework_MockObject_MockObject
  87.    */
  88.   protected $networkManager;
  89.  
  90.   /**
  91.    * {@inheritdoc}
  92.    *
  93.    * This method is called before running each test in this file. Basically,
  94.    * I want to have a "clear" instance of my mocked SocialApiController in each
  95.    * test. This method sets ups the desired mocked class.
  96.    */
  97.   public function setUp() {
  98.     parent::setUp();
  99.  
  100.     // The Drupal container must be initialized for string translation to work.
  101.     /** @var \Symfony\Component\DependencyInjection\ContainerInterface|\PHPUnit_Framework_MockObject_MockObject $container */
  102.     $container = $this->createMock(ContainerInterface::class);
  103.     \Drupal::setContainer($container);
  104.  
  105.     $this->networkManager = $this->getMockBuilder(NetworkManager::class)
  106.       ->disableOriginalConstructor()
  107.       ->getMock();
  108.  
  109.     $this->controller = $this->getMockBuilder(SocialApiController::class)
  110.       ->setConstructorArgs([$this->networkManager])
  111.       ->setMethods(NULL)
  112.       ->getMock();
  113.   }
  114.  
  115.   /**
  116.    * {@inheritdoc}
  117.    *
  118.    * This method is called after running each test in this file. In this case,
  119.    * I don't need to unset the property controller since it would be
  120.    * re-initialized by setUp anyways. However, this is an example of how you
  121.    * might use tearDown if you want to undo some changes after each test.
  122.    */
  123.   public function tearDown() {
  124.     unset($this->controller);
  125.   }
  126.  
  127.   /**
  128.    * Tests for class SocialApiController.
  129.    *
  130.    * To be honest, the only cases in which I will check if a method exists is
  131.    * for classes that implement an interface or inherit from an abstract class.
  132.    * Those are the only cases in which I believe checking for something like
  133.    * this is relatively useful.
  134.    */
  135.   public function testSocialApiController() {
  136.     $this->assertTrue(
  137.       method_exists($this->controller, 'create'),
  138.       'SocialApiController class does not implements create function/method'
  139.     );
  140.     $this->assertTrue(
  141.       method_exists($this->controller, 'integrations'),
  142.       'SocialApiController class does not implements integrations function/method'
  143.     );
  144.   }
  145.  
  146.   /**
  147.    * Tests that only integrations for Social Auth are rendered.
  148.    *
  149.    * This is the type of tests that are more useful. I'm actually testing if
  150.    * a method is behaving as I expect. In this case, given that I have
  151.    * integrations for both Social Auth and Social Post, I expect integrations
  152.    * to return a render array containing only the integrations for Social Auth.
  153.    */
  154.   public function testGetIntegrationsForSocialAuth() {
  155.  
  156.     // When calling SocialApiController::integrations, I'm expecting
  157.     // getDefinitions to be called once and return a non-empty array.
  158.     $this->networkManager->expects($this->once())
  159.       ->method('getDefinitions')
  160.       ->willReturn($this->getDefinitions());
  161.  
  162.     // The #rows property in the expected render array must be an array of the
  163.     // Social Auth integrations (without the type key) and converted to a
  164.     // non-associative array.
  165.     $rows = [];
  166.     foreach ($this->getSocialAuthDefinitions() as $integration) {
  167.       $rows[] = [
  168.         $integration['id'],
  169.         $integration['social_network'],
  170.       ];
  171.     }
  172.  
  173.     $expected = [
  174.       '#theme' => 'table',
  175.       '#header' => [
  176.         $this->t('Module'),
  177.         $this->t('Social Network'),
  178.       ],
  179.       '#rows' => $rows,
  180.       '#empty' => $this->t('There are no social integrations enabled.'),
  181.     ];
  182.  
  183.     // Now I call the method I'm interested in testing.
  184.     $integrations = $this->controller->integrations('social_auth');
  185.  
  186.     $this->assertEquals($expected, $integrations);
  187.   }
  188.  
  189.   /**
  190.    * Returns the mocked Network plugin definitions.
  191.    *
  192.    * @return array
  193.    *   The Network plugin definitions array.
  194.    */
  195.   protected function getDefinitions() {
  196.     return array_merge($this->getSocialAuthDefinitions(),
  197.                        $this->getSocialPostDefinitions());
  198.   }
  199.  
  200.   /**
  201.    * Returns the mocked Network plugin definitions for Social Auth.
  202.    *
  203.    * @return array
  204.    *   The Network plugin definitions array.
  205.    */
  206.   protected function getSocialAuthDefinitions() {
  207.     return [
  208.       [
  209.         'type' => 'social_auth',
  210.         'id' => 'social_auth_google',
  211.         'social_network' => 'Google',
  212.       ],
  213.       [
  214.         'type' => 'social_auth',
  215.         'id' => 'social_auth_facebook',
  216.         'social_network' => 'Facebook',
  217.       ],
  218.     ];
  219.   }
  220.  
  221.   /**
  222.    * Returns the mocked Network plugin definitions for Social Post.
  223.    *
  224.    * @return array
  225.    *   The Network plugin definitions array.
  226.    */
  227.   protected function getSocialPostDefinitions() {
  228.     return [
  229.       [
  230.         'type' => 'social_post',
  231.         'id' => 'social_post_google',
  232.         'social_network' => 'Google',
  233.       ],
  234.     ];
  235.   }
  236.  
  237. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement