Skip to content

Commit 62b6540

Browse files
authored
MQE-1087: Test _after hook does not run if test fails with exception
- TestContextExtension now handles error events, and runs _after block if the test errored out.
1 parent 00f473b commit 62b6540

File tree

1 file changed

+53
-13
lines changed

1 file changed

+53
-13
lines changed

src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class TestContextExtension extends \Codeception\Extension
2323
*/
2424
public static $events = [
2525
Events::TEST_FAIL => 'testFail',
26-
Events::STEP_AFTER => 'afterStep'
26+
Events::STEP_AFTER => 'afterStep',
27+
Events::TEST_END => 'testError'
2728
];
2829

2930
/**
@@ -38,22 +39,61 @@ public function testFail(\Codeception\Event\FailEvent $e)
3839
// Do not attempt to run _after if failure was in the _after block
3940
// Try to run _after but catch exceptions to prevent them from overwriting original failure.
4041
if ($context != TestContextExtension::TEST_PHASE_AFTER) {
41-
try {
42-
$actorClass = $e->getTest()->getMetadata()->getCurrent('actor');
43-
$I = new $actorClass($cest->getScenario());
44-
call_user_func(\Closure::bind(
45-
function () use ($cest, $I) {
46-
$cest->executeHook($I, 'after');
47-
},
48-
null,
49-
$cest
50-
));
51-
} catch (\Exception $e) {
52-
// Do not rethrow Exception
42+
$this->runAfterBlock($e, $cest);
43+
}
44+
}
45+
46+
/**
47+
* Codeception event listener function, triggered on test error.
48+
* @param \Codeception\Event\TestEvent $e
49+
* @return void
50+
*/
51+
public function testError(\Codeception\Event\TestEvent $e)
52+
{
53+
$cest = $e->getTest();
54+
55+
//Access private TestResultObject to find stack and if there are any errors (as opposed to failures)
56+
$testResultObject = call_user_func(\Closure::bind(
57+
function () use ($cest) {
58+
return $cest->getTestResultObject();
59+
},
60+
$cest
61+
));
62+
$errors = $testResultObject->errors();
63+
if (!empty($errors)) {
64+
$stack = $errors[0]->thrownException()->getTrace();
65+
$context = $this->extractContext($stack, $cest->getTestMethod());
66+
// Do not attempt to run _after if failure was in the _after block
67+
// Try to run _after but catch exceptions to prevent them from overwriting original failure.
68+
if ($context != TestContextExtension::TEST_PHASE_AFTER) {
69+
$this->runAfterBlock($e, $cest);
5370
}
5471
}
5572
}
5673

74+
/**
75+
* Runs cest's after block, if necessary.
76+
* @param Symfony\Component\EventDispatcher\Event $e
77+
* @param \Codeception\TestInterface $cest
78+
* @return void
79+
*/
80+
private function runAfterBlock($e, $cest)
81+
{
82+
try {
83+
$actorClass = $e->getTest()->getMetadata()->getCurrent('actor');
84+
$I = new $actorClass($cest->getScenario());
85+
call_user_func(\Closure::bind(
86+
function () use ($cest, $I) {
87+
$cest->executeHook($I, 'after');
88+
},
89+
null,
90+
$cest
91+
));
92+
} catch (\Exception $e) {
93+
// Do not rethrow Exception
94+
}
95+
}
96+
5797
/**
5898
* Extracts hook method from trace, looking specifically for the cest class given.
5999
* @param array $trace

0 commit comments

Comments
 (0)