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'), ]; });
2 Answers
Answers 1
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:
- unit tests (in case the code show above is one, with the focus on unit) should only test one specific aspect of your domain layer - thus, to ensure that
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" - integration tests (that's how the code above looks like) should on the other hand be tested with the real implementation (no "mocks" nor "prophecies" if possible), but on a faked/provided scenario - means if you want to test the whole application of factories, models and validators you should provide a complete testing scenario in the database for instance and perform your tests based on these
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:
Answers 2
You should use the same logic as when you are doing this in your application :
• Create an event
• Add a game to the event
• Add a team to the game
If you need to test your date, then just edit them after :)
0 comments:
Post a Comment