Monday, January 8, 2018

Laravel Modal Factories with Tests

Leave a Comment

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 and Team 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 :)

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment