Completed
Push — master ( 86db96...a2faf8 )
by Nicolas
08:49
created

ArrangeResultPrinter   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
c 1
b 0
f 0
lcom 1
cbo 5
dl 0
loc 120
ccs 0
cts 55
cp 0
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A startTest() 0 11 3
A getArrangeText() 0 16 2
A getArrangeLines() 0 18 4
A getArrangeDescription() 0 15 2
A getArrangeLine() 0 11 1
A getArgumentsAsString() 0 15 2
1
<?php
2
3
namespace Nikoms\PhpUnit\Printer;
4
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use Nikoms\PhpUnit\Annotation\Arrange;
7
use Nikoms\PhpUnit\AnnotationReaderFactory;
8
use Nikoms\PhpUnit\Listener\ArrangeListener;
9
use PHPUnit_Framework_Test;
10
11
/**
12
 * Class ArrangeResultPrinter
13
 * @package Nikoms\PhpUnit\Printer
14
 */
15
class ArrangeResultPrinter extends \PHPUnit_Util_TestDox_ResultPrinter_Text
16
{
17
    /**
18
     * @param \PHPUnit_Framework_Test | \PHPUnit_Framework_TestCase $test
19
     */
20
    public function startTest(PHPUnit_Framework_Test $test)
21
    {
22
        parent::startTest($test);
23
        if ($this->currentTestMethodPrettified == null || empty(ArrangeListener::$inputs[$test->getName(true)])) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PHPUnit_Framework_Test as the method getName() does only exist in the following implementations of said interface: AbstractTest, BankAccountTest, BankAccountWithCustomExtensionTest, BeforeAndAfterTest, BeforeClassAndAfterClassTest, ChangeCurrentWorkingDirectoryTest, ConcreteTest, ConcreteWithMyCustomExtensionTest, CountTest, CoverageClassExtendedTest, CoverageClassTest, CoverageFunctionParenthesesTest, CoverageFunctionParenthesesWhitespaceTest, CoverageFunctionTest, CoverageMethodOneLineAnnotationTest, CoverageMethodParenthesesTest, CoverageMethodParenthesesWhitespaceTest, CoverageMethodTest, CoverageNamespacedFunctionTest, CoverageNoneTest, CoverageNotPrivateTest, CoverageNotProtectedTest, CoverageNotPublicTest, CoverageNothingTest, CoveragePrivateTest, CoverageProtectedTest, CoveragePublicTest, DataProviderDebugTest, DataProviderFilterTest, DataProviderIncompleteTest, DataProviderSkippedTest, DataProviderTest, DependencyFailureTest, DependencySuccessTest, EmptyTestCaseTest, ExceptionInAssertPostConditionsTest, ExceptionInAssertPreConditionsTest, ExceptionInSetUpTest, ExceptionInTearDownTest, ExceptionInTest, ExceptionMessageRegExpTest, ExceptionMessageTest, ExceptionStackTest, ExceptionTest, Extensions_PhptTestCaseTest, Extensions_RepeatedTestTest, Failure, FailureTest, FatalTest, Foo_Bar_Issue684Test, Framework_AssertTest, Framework_BaseTestListenerTest, Framework_ConstraintTest, Framework_Constraint_JsonMatchesTest, Framework_Constraint_Jso...rrorMessageProviderTest, Framework_SuiteTest, Framework_TestCaseTest, Framework_TestFailureTest, Framework_TestImplementorTest, Framework_TestListenerTest, IncompleteTest, InheritanceA, InheritanceB, InheritedTestCase, IniTest, IsolationTest, Issue1021Test, Issue1149Test, Issue1216Test, Issue1265Test, Issue1330Test, Issue1335Test, Issue1337Test, Issue1348Test, Issue1351Test, Issue1374Test, Issue1437Test, Issue1468Test, Issue1471Test, Issue1472Test, Issue1570Test, Issue244Test, Issue322Test, Issue433Test, Issue445Test, Issue498Test, Issue503Test, Issue523Test, Issue578Test, Issue581Test, Issue74Test, Issue765Test, Issue797Test, MultiDependencyTest, My\Space\ExceptionNamespaceTest, NamespaceCoverageClassExtendedTest, NamespaceCoverageClassTest, NamespaceCoverageCoversClassPublicTest, NamespaceCoverageCoversClassTest, NamespaceCoverageMethodTest, NamespaceCoverageNotPrivateTest, NamespaceCoverageNotProtectedTest, NamespaceCoverageNotPublicTest, NamespaceCoveragePrivateTest, NamespaceCoverageProtectedTest, NamespaceCoveragePublicTest, NoArgTestCaseTest, NoTestCases, NotExistingCoveredElementTest, NotPublicTestCase, NotVoidTestCase, NothingTest, OneTest, OneTestCase, OutputTestCase, OverrideTestCase, PHPUnit_Extensions_GroupTestSuite, PHPUnit_Extensions_PhptTestCase, PHPUnit_Extensions_PhptTestSuite, PHPUnit_Framework_IncompleteTestCase, PHPUnit_Framework_SkippedTestCase, PHPUnit_Framework_TestCase, PHPUnit_Framework_TestSuite, PHPUnit_Framework_TestSuite_DataProvider, PHPUnit_Framework_Warning, PhpTestCaseProxy, RequirementsClassBeforeClassHookTest, RequirementsTest, Runner_BaseTestRunnerTest, StackTest, Success, TemplateMethodsTest, TestError, TestIncomplete, TestSkipped, TestWithTest, ThrowExceptionTestCase, ThrowNoExceptionTestCase, TwoTest, Util_ConfigurationTest, Util_GetoptTest, Util_GlobalStateTest, Util_RegexTest, Util_TestDox_NamePrettifierTest, Util_TestTest, Util_XMLTest, WasRun.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
24
            return;
25
        }
26
27
        $arranges = $this->getArrangeLines($test);
0 ignored issues
show
Compatibility introduced by
$test of type object<PHPUnit_Framework_Test> is not a sub-type of object<PHPUnit_Framework_TestCase>. It seems like you assume a concrete implementation of the interface PHPUnit_Framework_Test to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
28
        $arrangeText = $this->getArrangeText($arranges);
29
        $this->currentTestMethodPrettified = $arrangeText.$this->currentTestMethodPrettified;
30
    }
31
32
    /**
33
     * @param array $arranges
34
     * @return string
35
     */
36
    private function getArrangeText(array $arranges)
37
    {
38
        $arrangeText = '';
39
        $tab = '     ';
40
        if (!empty($arranges)) {
41
            $arrangeText = 'When '
42
                .implode(PHP_EOL.$tab.'And ', $arranges)
43
                .PHP_EOL
44
                .$tab
45
                .'Then ';
46
47
            return $arrangeText;
48
        }
49
50
        return $arrangeText;
51
    }
52
53
    /**
54
     * @param \PHPUnit_Framework_TestCase $test
55
     * @return array
56
     */
57
    private function getArrangeLines(\PHPUnit_Framework_TestCase $test)
58
    {
59
        $arranges = [];
60
        $annotationReader = AnnotationReaderFactory::getAnnotationReader();
61
62
        foreach (ArrangeListener::$inputs[$test->getName(true)] as $i => $arrangeMethods) {
63
            foreach ($arrangeMethods as $arrangeMethod => $arguments) {
64
                try {
65
                    $describe = $this->getArrangeDescription($test, $annotationReader, $arrangeMethod);
66
                    $arranges[] = $this->getArrangeLine($describe, $arguments);
67
                } catch (\DomainException $ex) {
68
                    continue;
69
                }
70
            }
71
        }
72
73
        return $arranges;
74
    }
75
76
    /**
77
     * @param PHPUnit_Framework_Test $test
78
     * @param AnnotationReader $annotationReader
79
     * @param string $arrangeMethod
80
     * @return string
81
     */
82
    private function getArrangeDescription(
83
        PHPUnit_Framework_Test $test,
84
        AnnotationReader $annotationReader,
85
        $arrangeMethod
86
    ) {
87
        $arrangeMethodAnnotation = $annotationReader->getMethodAnnotation(
88
            new \ReflectionMethod($test, $arrangeMethod),
89
            Arrange::class
90
        );
91
        if ($arrangeMethodAnnotation === null) {
92
            throw new \DomainException('The arrange method does not have an annotation itself');
93
        }
94
95
        return $arrangeMethodAnnotation->getMethods()['describe'];
96
    }
97
98
    /**
99
     * @param string $describe
100
     * @param array $arguments
101
     * @return array
102
     */
103
    private function getArrangeLine($describe, array $arguments)
104
    {
105
        return call_user_func_array(
106
            'sprintf',
107
            array_merge(
108
                [$describe],
109
                $this->getArgumentsAsString($arguments)
110
            )
111
        )
112
        .',';
113
    }
114
115
    /**
116
     * @param array $arguments
117
     * @return array
118
     */
119
    private function getArgumentsAsString(array $arguments)
120
    {
121
        return array_map(
122
            function ($argument) {
123
                if (is_object($argument)) {
124
                    $fullClassName = get_class($argument);
125
126
                    return 'the '.strtolower(substr($fullClassName, strrpos($fullClassName, '\\') + 1));
127
                }
128
129
                return $argument;
130
            },
131
            $arguments
132
        );
133
    }
134
}