1

I'm trying to create unit test for my application but I would like to have some advice.

I have these methods :

/**
 * This methods check if the user can apply to the team, it search if he have a username for the game and if he doesn't already applied for another team in the same tournament
 * @param User $user
 * @param Team $team
 * @return bool|string
 */
public function canApply(User $user, Team $team) {
    if ($user->getGamingUsername($team->getTournament()->getGame()) === null) {
        return "Vous devez avoir un nom d'utilisateur pour pouvoir vous inscrire, renseignez le dans \"Mon profil\"";
    } else if (false !== $teamAlreadyIn = $this->isAlreadyApplicant($user, $team)) {
        return "Vous avez déjà postulé pour une équipe pour ce tournoi : 
        <a href=\"".$this->router->generate("mgd_team_show", array("id" => $teamAlreadyIn->getId()))."\">".htmlspecialchars($teamAlreadyIn->getName())."</a>";
    }
    return true;
}

/**
 * This method search if the user is already in a team for the same tournament than the one passed in argument
 * @param User $user
 * @param Team $team
 * @return bool|Team|mixed
 */
public function isAlreadyApplicant($user, Team $team) {
    if (!$user || !$this->authorizationChecker->isGranted("ROLE_USER")) {
        return false;
    }

    foreach ($user->getApplications() as $userTeam) {
        /** @var Team $userTeam */
        if ($userTeam->getTournament()->getId() === $team->getTournament()->getId()) {
            return $userTeam;
        }
    }

    foreach ($user->getTeams() as $userTeam) {
        /** @var Team $userTeam */
        if ($userTeam->getTournament()->getId() === $team->getTournament()->getId()) {
            return $userTeam;
        }
    }

    foreach ($user->getManagedTeam() as $userTeam) {
        /** @var Team $userTeam */
        if ($userTeam->getTournament()->getId() === $team->getTournament()->getId()) {
            return $userTeam;
        }
    }
    return false;
}

As you can see, the first (canApply) call the second one (isAlreadyApplicant).

But when I try to test canApply, I have some trouble : this method call isAlreadyApplicant and in this one I compare based on the id the tournaments.

In my test class, I can't "->setId()" because it's a private method. So how am I supposed to handle this? Is it better to get an element from my database?

At the moment, one of my testMethod looks like this :

/**
 * The user is connected and try to apply to a team and applied for another team for another game
 */
public function testCanApplySecondTeamAnotherGame() {
    $user = new User();
    $game = new Game();
    $anotherGame = new Game();

    $team = new Team();
    $anotherTeam = new Team();

    $tournament = new TournamentTeam();
    $anotherTournament = new TournamentTeam();

    $team->setTournament($tournament);
    $anotherTeam->setTournament($anotherTournament);

    $tournament->setGame($game);
    $anotherTournament->setGame($anotherGame);

    $game->setName("TestGame");
    $anotherGame->setName("AnotherGame");

    $gamingProfile = new GamingProfile();
    $gamingProfile->setGame($game);
    $gamingProfile->setUsername("TestGameUsername");

    $anotherGamingProfile = new GamingProfile();
    $anotherGamingProfile->setGame($anotherGame);
    $anotherGamingProfile->setUsername("TestAnotherGameUsername");

    $user->setGamingProfiles(new ArrayCollection(array($gamingProfile, $anotherGamingProfile)));

    $user->addApplication($anotherTeam);
    $user->addTeam($anotherTeam);

    $router = $this->createMock(Router::class);
    $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class);
    $authorizationChecker->method("isGranted")->willReturn(true);

    $applicationChecker = new ApplicationChecker($router, $authorizationChecker);
    //Here, we try to apply to a team
    $this->assertTrue($applicationChecker->canApply($user, $team));

}

If you have any question, don't hesitate! Have a good day!

Alessandro Minoccheri
  • 35,521
  • 22
  • 122
  • 171
Alexandre
  • 355
  • 1
  • 3
  • 18

2 Answers2

1

This code is more coupled and will be difficult to test in an easy way.

I advise you to take method isAlreadyApplicant and move into a service for example.

After with the dependency injection inject the new service into your actual service (I hope is a service).

Dependency Injection Documentation

So now you can mock the new service with method isAlreadyApplicant.

This is for unit test.


If you need functional tests you can create a class, set the id and use it instead of create new Team() so you can control your class because you aren't testing the entity class but the function.


Another solution is to mock library to set private properties into your tests but I don't like this solution

Alessandro Minoccheri
  • 35,521
  • 22
  • 122
  • 171
  • Yes, this is in a same service but it won't change the problem, when I will test isAlreadyApplicant, I will have the same problem. If I have to create for each entity another entityTest, it will take a lot of class to perform it, I don't want to do this :/ – Alexandre Aug 10 '17 at 10:02
  • Ok but your code is too coupled, when you need to test isAlreadyApplicant you need to mock your entity with phpunit you don't need to craete class, I advise you to separate It and mock service and entity @Alexandre – Alessandro Minoccheri Aug 10 '17 at 10:03
  • When you say my code is too coupled, it's only for the unit test or in general? I'll seperate these two into two service, thanks for the advice. I managed to mock the entites and it work (but I'll split my service) – Alexandre Aug 10 '17 at 11:59
  • Code must be not coupled if is possible to be tested. You can have code coupled but after is difficult to test and sometimes is impossible. Take your code uncoupled if is possible and your tests will be very easy to create with mock. :) Glad to help you, accept the answer if this solved your problem please @Alexandre – Alessandro Minoccheri Aug 10 '17 at 12:02
0

Instead of using new User() in test, you should mock those objects. Then you can simulate any method in this, so you won't need to use $user->setId() as you will be able to get $userMock->method('getId')->willReturn(10).

So, instead of instantiate new objects and set values with setters, mock those objects and stub the getters.

See https://phpunit.de/manual/current/en/test-doubles.html for references.

Marçal Berga
  • 305
  • 1
  • 11