diff --git a/server/src/main/resources/application-mock-data-test.properties b/server/src/main/resources/application-mock-data-test.properties new file mode 100644 index 0000000..c68d6fa --- /dev/null +++ b/server/src/main/resources/application-mock-data-test.properties @@ -0,0 +1,18 @@ +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +# use one of these alternatives... +# ... purely in-memory, wiped on restart, but great for testing +spring.datasource.url=jdbc:h2:mem:testdb +# ... persisted on disk (in project directory) +#spring.datasource.url=jdbc:h2:file:./h2-database + +# enable DB view on http://localhost:8080/h2-console +spring.h2.console.enabled=true + +# strategy for table (re-)generation +spring.jpa.hibernate.ddl-auto=update +# show auto-generated SQL commands +#spring.jpa.hibernate.show_sql=true diff --git a/server/src/test/java/server/api/RecipeControllerTest.java b/server/src/test/java/server/api/RecipeControllerTest.java new file mode 100644 index 0000000..5931b3a --- /dev/null +++ b/server/src/test/java/server/api/RecipeControllerTest.java @@ -0,0 +1,137 @@ +package server.api; + + +import commons.Recipe; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import server.database.RecipeRepository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.LongStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// Spring Boot unit testing magic +// Before each test the state of the repository is reset by +// rolling back transaction of changes from previous test +@DataJpaTest + +// This test sources its application profile from +// resources/application-mock-data-test.properties +// This config uses an in-memory database +@ActiveProfiles("mock-data-test") +public class RecipeControllerTest { + private RecipeController controller; + private List recipes; + private RecipeRepository recipeRepository; + public static final int NUM_RECIPES = 10; + + // Injects a test repository into the test class + @Autowired + public RecipeControllerTest(RecipeRepository recipeRepository) { + this.recipeRepository = recipeRepository; + } + + @BeforeEach + public void setup() { + recipes = LongStream + .range(0, NUM_RECIPES) + .mapToObj(x -> new Recipe(null, "Recipe " + x, List.of(), List.of())) + .toList(); + controller = new RecipeController(recipeRepository); + } + @Test + public void createOneRecipe() { + controller.createRecipe(recipes.getFirst()); + + // There is 1 recipe in the repository + assertEquals(1, recipeRepository.count()); + } + @Test + public void createManyRecipes() { + recipes.forEach(recipe -> controller.createRecipe(recipe)); + + // There are the same number of recipes in the repository as the input list + assertEquals(recipes.size(), recipeRepository.count()); + } + @Test + public void getManyRecipes() { + recipes.forEach(recipe -> controller.createRecipe(recipe)); + + // The number of recipes returned is the same as the entire input list + assertEquals(recipes.size(), controller.getRecipes(Optional.empty()).getBody().size()); + } + @Test + public void getSomeRecipes() { + final int LIMIT = 5; + recipes.forEach(recipe -> controller.createRecipe(recipe)); + // The number of recipes returned is the same as the entire input list + assertEquals(LIMIT, controller.getRecipes(Optional.of(LIMIT)).getBody().size()); + } + @Test + public void findOneRecipeExists() { + final int CHECK_INDEX = 3; + List ids = LongStream + .range(0, NUM_RECIPES) + .map(recipe -> controller.createRecipe(recipes.get((int) recipe)).getBody().getId()) + .boxed().toList(); + + // The third item in the input list is the same as the third item retrieved from the database + assertEquals( + recipes.get(CHECK_INDEX), + controller.getRecipe(ids.get(CHECK_INDEX)).getBody()); + } + @Test + public void findOneRecipeNotExists() { + final int CHECK_INDEX = 3; + // There does not exist a recipe with ID=3 since there are no items in the repository. + assertEquals( + HttpStatus.NOT_FOUND, + controller.getRecipe((long) CHECK_INDEX).getStatusCode()); + } + @Test + public void deleteOneRecipeGood() { + final int DELETE_INDEX = 5; + List ids = LongStream + .range(0, NUM_RECIPES) + .map(recipe -> controller.createRecipe(recipes.get((int) recipe)).getBody().getId()) + .boxed().toList(); + + // The object has been successfully deleted + assertEquals(HttpStatus.OK, controller.deleteRecipe(ids.get(DELETE_INDEX)).getStatusCode()); + } + @Test + public void deleteOneRecipeCountGood() { + final int DELETE_INDEX = 5; + List ids = LongStream + .range(0, NUM_RECIPES) + .map(recipe -> controller.createRecipe(recipes.get((int) recipe)).getBody().getId()) + .boxed().toList(); + controller.deleteRecipe(ids.get(DELETE_INDEX)); + // The count of items decreased by 1 after the 5th item has been removed. + assertEquals(ids.size() - 1, recipeRepository.count()); + } + @Test + public void deleteOneRecipeFail() { + final Long DELETE_INDEX = 5L; + assertEquals(HttpStatus.BAD_REQUEST, controller.deleteRecipe(DELETE_INDEX).getStatusCode()); + } + + @Test + public void updateOneRecipeHasNewData() { + final int UPDATE_INDEX = 5; + List ids = LongStream + .range(0, NUM_RECIPES) + .map(recipe -> controller.createRecipe(recipes.get((int) recipe)).getBody().getId()) + .boxed().toList(); + Recipe newRecipe = controller.getRecipe(ids.get(UPDATE_INDEX)).getBody(); + newRecipe.setName("New recipe"); + controller.updateRecipe(newRecipe.getId(), newRecipe); + assertEquals("New recipe", recipeRepository.getReferenceById(ids.get(UPDATE_INDEX)).getName()); + } +}