zaseknutí testu Kdyby/Console commandu

Petr Parolek
Člen | 455
+
0
-

Ahoj,

už si nevím rady s testem:

<?php

namespace Tests\App\Presenters;

require __DIR__ . '/../bootstrap.php';

use App\Commands\AddUserCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Tester\CommandTester;

class AddUserCommandTest extends \Tester\TestCase
{

	use \Testbench\TCompiledContainer;
	use \Testbench\TDoctrine;

	public function testExecute()
	{
		$console = $this->getService(\Symfony\Component\Console\Application::class);

		$command = $console->find('app:add-user');

		$commandTester = new CommandTester($command);

		// Equals to a user inputting "This", "That" and hitting ENTER
		// This can be used for answering two separated questions for instance
		$commandTester->setInputs([
			'testxxx',
			'testxxx@example.com',
			'Test',
			'Xxx',
			'test role',
			'1',
			'password',
			'password',
		]);

		$commandTester->execute([]);
		$output = $commandTester->getDisplay();

		\Tester\Assert::match('#was successfully created#i', $output);
	}
}

(new AddUserCommandTest)->run();
<?php

namespace App\Commands;

use App\Model\Repositories\UsersRepository;
use App\Model\Repositories\UserRolesRepository;
use App\Model\Services\UserService;
use Nette\Utils\Validators;
use Nette\Utils\Strings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;

class AddUserCommand extends Command
{

	/** @var UsersRepository */
	private $usersRepository;

	/** @var UserRolesRepository */
	private $userRolesRepository;

	/** @var UserService */
	private $userService;

	public function __construct(UsersRepository $usersRepository, UserRolesRepository $userRolesRepository, UserService $userService)
	{
		parent::__construct();

		$this->usersRepository = $usersRepository;
		$this->userRolesRepository = $userRolesRepository;
		$this->userService = $userService;
	}

	protected function configure()
	{
		$this->setName('app:add-user')
			->setDescription('Add new user');
	}

	protected function execute(InputInterface $input, OutputInterface $output)
	{
		/* @var $questionHelper QuestionHelper */
		$questionHelper = $this->getHelper('question');

		$usersRepository = $this->usersRepository;

		$usernameQuestion = new Question('Userame: ');
		$usernameQuestion->setValidator(function ($value) use ($usersRepository) {
			if (trim($value) === '') {
				throw new \Exception('Username can not be empty');
			}

			if (Strings::webalize($value) != $value) {
				throw new \Exception('Username contains unexcepted characters');
			}

			$username = $usersRepository->findBy(['username' => $value]);

			if ($username) {
				throw new \Exception('Username is already registered');
			}

			return $value;
		});

		$values['username'] = $questionHelper->ask($input, $output, $usernameQuestion);

		$emailQuestion = new Question('E-mail: ');
		$emailQuestion->setValidator(function ($value) use($usersRepository) {
			if (trim($value) === '') {
				throw new \Exception('E-mail can not be empty');
			}
			if (!Validators::isEmail($value)) {
				throw new \Exception('E-mail is not valid');
			}

			$email = $usersRepository->findBy(['email' => $value]);

			if ($email) {
				throw new \Exception('E-mail address is already registered');
			}

			return $value;
		});

		$values['email'] = $questionHelper->ask($input, $output, $emailQuestion);

		$firstNaneQuestion = new Question('First name: ');
		$firstNaneQuestion->setValidator(function ($value) {
			if (trim($value) === '') {
				throw new \Exception('First name can not be empty');
			}

			return $value;
		});

		$values['firstName'] = $questionHelper->ask($input, $output, $firstNaneQuestion);

		$lastNaneQuestion = new Question('Last name: ');
		$lastNaneQuestion->setValidator(function ($value) {
			if (trim($value) === '') {
				throw new \Exception('Last name can not be empty');
			}

			return $value;
		});

		$values['lastName'] = $questionHelper->ask($input, $output, $lastNaneQuestion);

		$companRoleQuestion = new Question('Company role: ');
		$companRoleQuestion->setValidator(function ($value) {
			if (trim($value) === '') {
				throw new \Exception('Companz role can not be empty');
			}

			return $value;
		});

		$values['companyRole'] = $questionHelper->ask($input, $output, $companRoleQuestion);

		$values['recoveryToken'] = null;

		$userRoles = $this->userRolesRepository->findPairs('name');

		$userRoleQuestion = new ChoiceQuestion(
			'Please select user role',
			$userRoles
		);
		$userRoleQuestion->setErrorMessage('User role %s is invalid.');

		$userRole = $questionHelper->ask($input, $output, $userRoleQuestion);
		//dump($userRole);
		$values['userRole'] = $this->userRolesRepository->findOneBy(['name' => $userRole]);

		$passwordQuestion = new Question('Password: ');
		$passwordQuestion->setValidator(function ($value) {
			if (trim($value) === '') {
				dump($value);
				throw new \Exception('The password can not be empty');
			}

			return $value;
		});

		$passwordQuestion->setHidden(TRUE);
		$passwordQuestion->setHiddenFallback(FALSE);

		$values['password'] = $questionHelper->ask($input, $output, $passwordQuestion);
		$password = $values['password'];

		$confirmPasswordQuestion = new Question('Confirm password: ');
		$confirmPasswordQuestion->setValidator(function ($value) use ($password) {
			if ($value != $password) {
				throw new \Exception("The password doesn't match");
			}

			return $value;
		});

		$confirmPasswordQuestion->setHidden(TRUE);
		$confirmPasswordQuestion->setHiddenFallback(FALSE);

		$questionHelper->ask($input, $output, $confirmPasswordQuestion);

		$values = \Nette\Utils\ArrayHash::from($values);
		$user = $this->userService->insertUser($values);

		$output->writeln('User ' . $user->getUsername() . ' was successfully created!');
	}
}

Když zakomentuju:

		$passwordQuestion->setHidden(TRUE);
		$passwordQuestion->setHiddenFallback(FALSE);

nebo

		$passwordQuestion->setValidator(function ($value) {
			if (trim($value) === '') {
				dump($value);
				throw new \Exception('The password can not be empty');
			}

			return $value;
		});

tak test proběhne ok, jinak se zasekne a nic netestuje. Prosím, kde je chyba?

jiri.pudil
Nette Blogger | 1028
+
0
-

Problém je v tom, že pokud nemá QuestionHelper k dispozici stty, ptá se uživatele na hidden response spuštěním systémové (nebo na Windows dokonce vlastní) binárky, která prostě čeká na uživatelský vstup, test netest. No a pokud test spustíš přes runner Nette Testeru, běží v podprocesu mimo terminál, kde stty selže. Pokud test pustíš samostatně napřímo přes php, projde. Taky jsem na to narazil a zatím jsem to nedokázal vyřešit.

Petr Parolek
Člen | 455
+
0
-

zajímavé, používám vagrant, takže linux. Jak mám teda spustit test?

jiri.pudil
Nette Blogger | 1028
+
0
-

Takto: php AddUserCommandTest.phpt

jiri.pudil
Nette Blogger | 1028
+
0
-

A v testu mám

\exec('stty 2>&1', $output, $exitCode);
if ($exitCode !== 0) {
	\Tester\Environment::skip('Cannot read hidden response if not running in a terminal with stty');
}

takže se v případě nepříznivého prostředí raději přeskočí, než aby čekal na vstup, který nikdy nepřijde.

Petr Parolek
Člen | 455
+
0
-

OK – vyřešeno workaroundem – nápad borce ze symfony eng slacku:

ve třídě commandu přidat:

private $disableHiddenQuestion = false;

public function disableHiddenQuestion($disable = true)
{
    $this->disableHiddenQuestion = $disable;

}

...

if (false === $this->disableHiddenQuestion) {

        $passwordQuestion->setHidden(true);

        $passwordQuestion->setHiddenFallback(true);

}

a do testu přidat:

$command->disableHiddenQuestion();