I'm trying to create a team and then add that team to a game and then add that game to the event, however, when the game is created it auto generates an event to attach to the event. In normal circumstances this is fine but because I'm testing the team's joined at compared to the event their first game is on then I need to create an event with a specific date. I can't create the event first because the game has to be created first to be able to add it to it.
Does anyone have a suggestion on correcting my logic so that I can get a game and event created correctly?
I don't know what I should need to do for this edge case.
/** @test */
public function a_team_with_a_game_after_they_started_the_season_cannot_have_their_joined_at_date_after_their_first_match()
{
$team = factory(Team::class)->create(['joined_at' => '2017-10-08']);
$game = GameFactory::create([], [$team]);
$event = EventFactory::create(['date' => '2017-10-09'], null, $game);
$validator = new BeforeFirstGameDate($team);
$this->assertFalse($validator->passes('joined_at', '2017-10-10'));
$this->assertEquals('The joined at date cannot be AFTER the team\'s first game.', $validator->message());
}
Factories
<?php
use App\Models\Game;
use App\Models\Team;
class GameFactory
{
public static function create($overrides = [], $teams = [])
{
$match = factory(Game::class)->create($overrides);
self::addTeamsForGame($teams, $game);
return $game;
}
/**
* @param $teams
* @param $game
*/
public static function addTeamsForGame($teams, $game)
{
$teamsForGame = [];
$numberOfTeamsToAdd = $numberOfTeams - count($teams);
if ($numberOfTeamsToAdd) {
$teamsForMatch = factory(Team::class, $numberOfTeamsToAdd)->create();
array_push($teams, $teamsForGame);
} else {
array_push($teams, $teamsForGame);
}
$match->addTeams($teamsForGame);
}
}
<?php
use App\Models\Event;
class EventFactory
{
public static function create($overrides = [], $totalNumberOfGames = 8, $games = [])
{
$event = factory(Event::class)->create($overrides);
$numberOfGamesToAdd = $totalNumberOfGames - count($games);
$gameToStartAt = count($games) + 1;
foreach (array_wrap($games) as $game) {
$game->addToEvent($event);
}
for ($gameNumber = $gameToStartAt; $gameNumber <= $numberOfGamesToAdd; $gameNumber++) {
GameFactory::create(['event_id' => $event->id, 'game_number' => $gameNumber]);
}
return $event;
}
}
$factory->define(App\Models\Game::class, function (Faker\Generator $faker) {
static $order = 1;
return [
'event_id' => function () {
return factory(App\Models\Event::class)->create()->id;
},
'game_number' => $order++,
];
});
$factory->define(App\Models\Event::class, function (Faker\Generator $faker) {
$name = $faker->sentence;
return [
'name' => $name,
'slug' => str_slug($name),
'date' => $faker->dateTimeBetween('-10 years'),
];
});
Note: The source code shown below is based on some assumptions since the specific implementation of models like for instance
Event
,Game
andTeam
were not given in the original question. Consider adding a Git repository containing the sources to the question to get more specific answers that reflects your implementation in more detail.
First, some general remarks to tests:
BeforeFirstGameDate
is behaving correctly but not testing a combination of possible involved services (database) or factories for object reconstitution - the techniques to be used are "mocks" or "prophecies"That being said and putting the focus more on the unit part in "unit test" your tests might look like - focussing on testing single, separated units and not a combination of them:
/** @test */
public function eventIsCreated()
{
...
static::assertSame($expectedEvent, EventFactory::create(...));
}
/** @test */
public function teamIsCreated()
{
...
static::assertSame($expectedTeam, TeamFactory::create(...));
}
/** @test */
public function gameIsCreated()
{
...
static::assertSame($expectedGame, GameFactory::create(...));
}
/** @test */
public function beforeFirstGameDateValidatorRejectsLateApplications()
{
...
}
The mentioned test case for BeforeFirstGameDate
validation might just look like the following then, using prophecies - the instance to be tested is named as $subject
to make it clear, what's the subject to be tested (as common best practice in writing tests):
/**
* @test
*/
public function beforeFirstGameDateValidatorRejectsLateApplications()
{
$event = $this->prophesize(Event::class);
$game = $this->prophesize(Game::class);
$team = $this->prophesize(Team::class);
$event->getGames()->willReturn([$game->reveal()]);
$game->getTeams()->willReturn([$team->reveal()]);
$team->get('joined_at')->willReturn('2017-10-08');
$subject = new BeforeFirstGameDate($team->reveal());
static::assertFalse(
$subject->passes('joined_at', '2017-10-10')
);
}
This way, your Event
, Game
and Team
models don't rely on any factory implementation anymore, but simulate properties and behavior using prophecies. Thus, in case factories get changed or refactored you only have to adjust those tests that assert object reconsitution - the mentioned beforeFirstGameDateValidatorRejectsLateApplications
can be skipped since it does not have a hard dependency on these factories for instance.
As mentioned in the beginning the methods and properties for Event
, Game
and Team
are just assumptions since the real implementation was unknown at the time of writing this answer.
References: