added IngredientControllerTest
This commit is contained in:
parent
5915b658e6
commit
0acbb13a49
2 changed files with 175 additions and 1 deletions
|
|
@ -23,7 +23,7 @@ public class IngredientController {
|
|||
private final RestTemplate restTemplate = new RestTemplate(); // Simplified REST client
|
||||
|
||||
@FXML
|
||||
private void handleDeleteIngredient(ActionEvent event) {
|
||||
void handleDeleteIngredient(ActionEvent event) {
|
||||
// Get selected ingredient
|
||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
if (selectedIngredient == null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
package client.Ingredient;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||
import commons.Ingredient;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@WireMockTest(httpPort = 8080)
|
||||
class IngredientControllerMockTest {
|
||||
|
||||
private IngredientController controller;
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
// starting javaFX and allow use of listview and alert usage
|
||||
@BeforeAll
|
||||
static void initJavaFx() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
Platform.startup(latch::countDown);
|
||||
} catch (IllegalStateException alreadyStarted) {
|
||||
latch.countDown();
|
||||
}
|
||||
assertTrue(latch.await(3, TimeUnit.SECONDS), "JavaFX Platform failed to start");
|
||||
Platform.setImplicitExit(false);
|
||||
}
|
||||
//inject fxml fields and create controller + mock UI
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
controller = new IngredientController();
|
||||
|
||||
ingredientListView = new ListView<>();
|
||||
ingredientListView.setItems(FXCollections.observableArrayList(
|
||||
new Ingredient("Bread", 1, 2, 3),
|
||||
new Ingredient("Cheese", 2, 2, 2),
|
||||
new Ingredient("Ham", 3, 3, 3)
|
||||
));
|
||||
|
||||
setPrivateField(controller, "ingredientListView", ingredientListView);
|
||||
setPrivateField(controller, "deleteButton", new Button("Delete"));
|
||||
}
|
||||
|
||||
// pick ingredient -> backend says not in use -> fake delete ingredient
|
||||
@Test
|
||||
void deleteingredientwhennotusedcallsusagethendeleteandclearslist() throws Exception {
|
||||
Ingredient selected = ingredientListView.getItems().get(0);
|
||||
ingredientListView.getSelectionModel().select(selected);
|
||||
|
||||
stubFor(get(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage"))
|
||||
.willReturn(okJson("{\"ingredientId\":" + selected.getId() + ",\"usedInRecipes\":0}")));
|
||||
|
||||
stubFor(delete(urlEqualTo("/api/ingredients/" + selected.getId()))
|
||||
.willReturn(ok()));
|
||||
|
||||
// safe close for show and wait, run controller on JavaFX
|
||||
try (DialogCloser closer = startDialogCloser()) {
|
||||
runOnFxThreadAndWait(() -> controller.handleDeleteIngredient(new ActionEvent()));
|
||||
}
|
||||
|
||||
verify(getRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage")));
|
||||
verify(deleteRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId())));
|
||||
|
||||
assertEquals(0, ingredientListView.getItems().size());
|
||||
}
|
||||
|
||||
//select ingredient -> if used backend says it and show warning -> safety delete but shouldn't happen
|
||||
@Test
|
||||
void deleteIngredientwhenUsedshowsWarningandDoesNotDeleteifDialogClosed() throws Exception {
|
||||
Ingredient selected = ingredientListView.getItems().get(1);
|
||||
ingredientListView.getSelectionModel().select(selected);
|
||||
|
||||
stubFor(get(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage"))
|
||||
.willReturn(okJson("{\"ingredientId\":" + selected.getId() + ",\"usedInRecipes\":2}")));
|
||||
|
||||
stubFor(delete(urlEqualTo("/api/ingredients/" + selected.getId()))
|
||||
.willReturn(ok()));
|
||||
|
||||
//safe close as if user selected cancel
|
||||
try (DialogCloser closer = startDialogCloser()) {
|
||||
runOnFxThreadAndWait(() -> controller.handleDeleteIngredient(new ActionEvent()));
|
||||
}
|
||||
|
||||
// check usage but not delete
|
||||
verify(getRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage")));
|
||||
verify(0, deleteRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId())));
|
||||
|
||||
assertEquals(3, ingredientListView.getItems().size());
|
||||
}
|
||||
|
||||
// fxml helper
|
||||
private static void setPrivateField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field f = target.getClass().getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
f.set(target, value);
|
||||
}
|
||||
|
||||
//controller on JavaFX
|
||||
private static void runOnFxThreadAndWait(Runnable action) throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
action.run();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
assertTrue(latch.await(8, TimeUnit.SECONDS), "FX action timed out");
|
||||
}
|
||||
|
||||
// safe close so that show and wait doesn't wait forever
|
||||
private static DialogCloser startDialogCloser() {
|
||||
AtomicBoolean running = new AtomicBoolean(true);
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
while (running.get()) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
for (Window w : Window.getWindows()) {
|
||||
if (w instanceof Stage stage && stage.isShowing()) {
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "javafx-dialog-closer");
|
||||
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
return new DialogCloser(running);
|
||||
}
|
||||
|
||||
// dialog closer
|
||||
private static final class DialogCloser implements AutoCloseable {
|
||||
private final AtomicBoolean running;
|
||||
|
||||
private DialogCloser(AtomicBoolean running) {
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
running.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue