package fr.enssat.BoulderDash.models; import fr.enssat.BoulderDash.helpers.AudioLoadHelper; import fr.enssat.BoulderDash.helpers.LevelLoadHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Arrays; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; public class LevelModelTest { private LevelModel levelModel; DisplayableElementModel[][] groundLevelModel; LevelLoadHelper levelLoadHelper; AudioLoadHelper audioLoadHelper; GameInformationModel gameInformationModel; @BeforeEach public void setUp() { levelLoadHelper = mock(LevelLoadHelper.class); audioLoadHelper = mock(AudioLoadHelper.class); gameInformationModel = mock(GameInformationModel.class); // boundaries and 2x2 field inside when(levelLoadHelper.getHeightSizeValue()).thenReturn(4); when(levelLoadHelper.getWidthSizeValue()).thenReturn(4); when(levelLoadHelper.getRockfordPositionX()).thenReturn(1); when(levelLoadHelper.getRockfordPositionY()).thenReturn(1); when(levelLoadHelper.getRockfordInstance()).thenReturn(new RockfordModel()); when(levelLoadHelper.getGroundGrid()).thenReturn( new DisplayableElementModel[4][4] ); // We add one diamond below, thus return 1. when(levelLoadHelper.getDiamondsToCatch()).thenReturn(1); levelModel = new LevelModel(levelLoadHelper, audioLoadHelper, "game"); // Place diamond next to Rockford levelModel.getGroundLevelModel()[1][2] = new DiamondModel(); groundLevelModel = levelModel.getGroundLevelModel(); } /** * During an ongoing game, the player character is moved to an exit. The following * behavior is expected and should be verified: * • The game ends. * • The player character is located on the field where the exit was previously located. * • No sound is played. */ @Test void test1bi(){ // Place exit next to Rockford levelModel.getGroundLevelModel()[2][2] = new DoorModel(); // Move Rockford from (1,1) to (2,2) levelModel.setPositionOfRockford(2,2); // Verify: // Game ended assertFalse(levelModel.isGameRunning()); // Rockford is located at exit/door assertEquals(2, levelModel.getRockfordPositionX()); assertEquals(2,levelModel.getRockfordPositionY()); // The sounds are played in a different thread after an action occurred. // This might happen after we verify the number of method calls. // To avoid this, we sleep one second. try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } // No sound is played verify(audioLoadHelper, never()).playSound(anyString()); } /** * During an ongoing game, the player character collects the last diamond. The * following behavior is expected and should be verified: * • The score increases by 1. * • The number of remaining diamonds decreases to 0. * • Exactly one exit appears on the game field. * • The player character is located on the field where the diamond was previously located. * • The starting field of the player character becomes an empty field. * • The sound for collecting a diamond is played. */ @Test void test1bii(){ int oldScore = levelModel.getGameInformationModel().getScore(); int oldRemainingDiamonds = levelModel.getGameInformationModel().getRemainingsDiamonds(); // Verify: // 1 Remaining diamond assertEquals(1, oldRemainingDiamonds); // No exit. assertEquals(0, numOfExits()); // Move Rockford from (1,1) to (1,2) levelModel.setPositionOfRockford(1,2); // Verify: // Score increased by 1 assertEquals(oldScore + 1, levelModel.getGameInformationModel().getScore()); // No remaining diamonds assertEquals(0, levelModel.getGameInformationModel().getRemainingsDiamonds()); // Verify: One exit appeared. // Math.random -> sometimes spawned at old or new position of Rockford, // then this assertion fails assertEquals(1, numOfExits()); // Rockford placed at previous diamond location assertEquals(1, levelModel.getRockfordPositionX()); assertEquals(2,levelModel.getRockfordPositionY()); // The starting field of Rockford becomes an empty field assertInstanceOf(EmptyModel.class, groundLevelModel[1][1]); // The sounds are played in a different thread after an action occurred. // This might happen after we verify the number of method calls (which would make this test fail). // To avoid this, we sleep one second. try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } // The sound for collecting a diamond is played. verify(audioLoadHelper).playSound("coin"); } int numOfExits(){ // We can't use // verify(levelModel, times(1)).spawnExit(); // as spawnExit() is a private method. // Iterate over the ground level and count DoorModel objects. return (int) Arrays.stream(groundLevelModel) .flatMap(Arrays::stream) .filter(x -> x instanceof DoorModel) .count(); } /** * During an ongoing game, the player character collects the last diamond again. * However, this test case should minimize concrete state changes in the application and instead verify that the appropriate methods are called with the * correct parameters to achieve the desired behavior. Explain and justify for * which of the following points this is not possible. The following behavior is * expected and should be verified: * • The increase in score is triggered. * • The decrease in the number of remaining diamonds is triggered. * • The appearance of an exit on the game field is triggered. * • The update of the player character’s position is triggered. * • Replacing the player character’s starting field with an empty field is triggered. * • Playing the sound for the entered field is triggered. */ @Test void test1biii(){ GameInformationModel gameInformationModel = mock(GameInformationModel.class); levelModel.setGameInformationModel(gameInformationModel); levelModel = spy(levelModel); // Move Rockford from (1,1) to (1,2) levelModel.setPositionOfRockford(1,2); // Verify: // increase score verify(gameInformationModel).incrementScore(); // decrease remaining diamonds verify(gameInformationModel).decrementRemainingsDiamonds(); // exit appearance - IMPOSSIBLE // 'spawnExit()' has private access // We can only verify there are no remaining diamonds verify(gameInformationModel).getRemainingsDiamonds(); // update character's position verify(levelModel).updateRockfordPosition(anyInt(), anyInt()); // replace with empty field - IMPOSSIBLE // We can't verify if the field at the position is being emptied, // because this happens inside the method setPositionOfRockford using a constructor call: // `this.groundGrid[oldX][oldY] = new EmptyModel();` assertInstanceOf(EmptyModel.class, groundLevelModel[1][1]); // The sounds are played in a different thread after an action occurred. // This might happen after we verify the number of method calls (which would make this test fail). // To avoid this, we sleep one second. try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } // The sound for collecting a diamond is played. verify(audioLoadHelper).playSound("coin"); } }