diff --git a/README.md b/README.md index 10b9111..b53eee4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ It is part of the HappyCoders tutorial series on Hexagonal Architecture: * [Part 2: Hexagonal Architecture with Java - Tutorial](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture-java/). * [Part 3: Ports and Adapters Java Tutorial: Adding a Database Adapter](https://www.happycoders.eu/software-craftsmanship/ports-and-adapters-java-tutorial-db/). * [Part 4: Hexagonal Architecture with Quarkus - Tutorial](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture-quarkus/). +* [Part 5: Hexagonal Architecture with Spring Boot - Tutorial](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture-spring-boot/). # Branches @@ -33,7 +34,7 @@ In the `with-quarkus` branch, you'll find an implementation using [Quarkus](http ## `with-spring` -There will soon be an additional branch with an implementation using [Spring](https://spring.io/) instead of Quarkus. +In the `with-quarkus` branch, you'll find an implementation using [Spring](https://spring.io/) as application framework. # Architecture Overview @@ -51,22 +52,25 @@ The `model` module is not represented as a hexagon because it is not defined by # How to Run the Application -The easiest way to run the application is to start the `main` method of the `Launcher` class (you'll find it in the `boostrap` module) from your IDE. +You can run the application in Quarkus dev mode with the following command: + +```shell +mvn test-compile quarkus:dev +``` You can use one of the following VM options to select a persistence mechanism: * `-Dpersistence=inmemory` to select the in-memory persistence option (default) * `-Dpersistence=mysql` to select the MySQL option -If you selected the MySQL option, you will need a running MySQL database. The easiest way to start one is to use the following Docker command: +For example, to run the application in MySQL mode, enter: ```shell -docker run --name hexagon-mysql -d -p3306:3306 \ - -e MYSQL_DATABASE=shop -e MYSQL_ROOT_PASSWORD=test mysql:8.1 +mvn test-compile quarkus:dev -Dpersistence=mysql ``` -The connection parameters for the database are hardcoded in `RestEasyUndertowShopApplication.initMySqlAdapter()`. If you are using the Docker container as described above, you can leave the connection parameters as they are. Otherwise, you may need to adjust them. - +In dev mode, Quarkus will automatically start a MySQL database using Docker, +and it will automatically create all database tables. # Example Curl Commands diff --git a/adapter/pom.xml b/adapter/pom.xml index 011f30f..189ccd7 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -23,53 +23,44 @@ - jakarta.persistence - jakarta.persistence-api + io.quarkus + quarkus-arc - jakarta.ws.rs - jakarta.ws.rs-api + io.quarkus + quarkus-hibernate-orm - - - mysql - mysql-connector-java - test + io.quarkus + quarkus-hibernate-orm-panache - io.rest-assured - rest-assured - test + io.quarkus + quarkus-jdbc-mysql - org.jboss.resteasy - resteasy-jackson2-provider - test + io.quarkus + quarkus-resteasy - org.glassfish - jakarta.el - test - - - org.hibernate.orm - hibernate-core - test + io.quarkus + quarkus-resteasy-jackson + + - org.hibernate.validator - hibernate-validator + io.quarkus + quarkus-junit5 test - org.jboss.resteasy - resteasy-undertow + io.quarkus + quarkus-junit5-mockito test - org.testcontainers - mysql + io.rest-assured + rest-assured test diff --git a/adapter/src/main/java/eu/happycoders/shop/QuarkusAppConfig.java b/adapter/src/main/java/eu/happycoders/shop/QuarkusAppConfig.java new file mode 100644 index 0000000..9eacf0f --- /dev/null +++ b/adapter/src/main/java/eu/happycoders/shop/QuarkusAppConfig.java @@ -0,0 +1,47 @@ +package eu.happycoders.shop; + +import eu.happycoders.shop.application.port.in.cart.AddToCartUseCase; +import eu.happycoders.shop.application.port.in.cart.EmptyCartUseCase; +import eu.happycoders.shop.application.port.in.cart.GetCartUseCase; +import eu.happycoders.shop.application.port.in.product.FindProductsUseCase; +import eu.happycoders.shop.application.port.out.persistence.CartRepository; +import eu.happycoders.shop.application.port.out.persistence.ProductRepository; +import eu.happycoders.shop.application.service.cart.AddToCartService; +import eu.happycoders.shop.application.service.cart.EmptyCartService; +import eu.happycoders.shop.application.service.cart.GetCartService; +import eu.happycoders.shop.application.service.product.FindProductsService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +class QuarkusAppConfig { + + @Inject Instance cartRepository; + + @Inject Instance productRepository; + + @Produces + @ApplicationScoped + GetCartUseCase getCartUseCase() { + return new GetCartService(cartRepository.get()); + } + + @Produces + @ApplicationScoped + EmptyCartUseCase emptyCartUseCase() { + return new EmptyCartService(cartRepository.get()); + } + + @Produces + @ApplicationScoped + FindProductsUseCase findProductsUseCase() { + return new FindProductsService(productRepository.get()); + } + + @Produces + @ApplicationScoped + AddToCartUseCase addToCartUseCase() { + return new AddToCartService(cartRepository.get(), productRepository.get()); + } +} diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepository.java index 3d8a373..0cb5baa 100644 --- a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepository.java +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepository.java @@ -3,6 +3,8 @@ import eu.happycoders.shop.application.port.out.persistence.CartRepository; import eu.happycoders.shop.model.cart.Cart; import eu.happycoders.shop.model.customer.CustomerId; +import io.quarkus.arc.lookup.LookupIfProperty; +import jakarta.enterprise.context.ApplicationScoped; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -12,6 +14,8 @@ * * @author Sven Woltmann */ +@LookupIfProperty(name = "persistence", stringValue = "inmemory", lookupIfMissing = true) +@ApplicationScoped public class InMemoryCartRepository implements CartRepository { private final Map carts = new ConcurrentHashMap<>(); diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepository.java index 3bef51d..3179707 100644 --- a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepository.java +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepository.java @@ -4,6 +4,8 @@ import eu.happycoders.shop.application.port.out.persistence.ProductRepository; import eu.happycoders.shop.model.product.Product; import eu.happycoders.shop.model.product.ProductId; +import io.quarkus.arc.lookup.LookupIfProperty; +import jakarta.enterprise.context.ApplicationScoped; import java.util.List; import java.util.Locale; import java.util.Map; @@ -15,6 +17,8 @@ * * @author Sven Woltmann */ +@LookupIfProperty(name = "persistence", stringValue = "inmemory", lookupIfMissing = true) +@ApplicationScoped public class InMemoryProductRepository implements ProductRepository { private final Map products = new ConcurrentHashMap<>(); diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/EntityManagerFactoryFactory.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/EntityManagerFactoryFactory.java deleted file mode 100644 index f06b17f..0000000 --- a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/EntityManagerFactoryFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package eu.happycoders.shop.adapter.out.persistence.jpa; - -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.Persistence; -import java.util.Map; - -/** - * Factory for an EntityManagerFactory for connecting to a MySQL database. - * - * @author Sven Woltmann - */ -public final class EntityManagerFactoryFactory { - - private EntityManagerFactoryFactory() {} - - public static EntityManagerFactory createMySqlEntityManagerFactory( - String jdbcUrl, String user, String password) { - return Persistence.createEntityManagerFactory( - "eu.happycoders.shop.adapter.out.persistence.jpa", - Map.of( - "hibernate.dialect", - "org.hibernate.dialect.MySQLDialect", - "hibernate.hbm2ddl.auto", - "update", - "jakarta.persistence.jdbc.driver", - "com.mysql.jdbc.Driver", - "jakarta.persistence.jdbc.url", - jdbcUrl, - "jakarta.persistence.jdbc.user", - user, - "jakarta.persistence.jdbc.password", - password)); - } -} diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartPanacheRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartPanacheRepository.java new file mode 100644 index 0000000..f565e3c --- /dev/null +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartPanacheRepository.java @@ -0,0 +1,12 @@ +package eu.happycoders.shop.adapter.out.persistence.jpa; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Panache repository for {@link CartJpaEntity}. + * + * @author Sven Woltmann + */ +@ApplicationScoped +public class JpaCartPanacheRepository implements PanacheRepositoryBase {} diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepository.java index 7435e90..c5192e3 100644 --- a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepository.java +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepository.java @@ -3,8 +3,9 @@ import eu.happycoders.shop.application.port.out.persistence.CartRepository; import eu.happycoders.shop.model.cart.Cart; import eu.happycoders.shop.model.customer.CustomerId; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; +import io.quarkus.arc.lookup.LookupIfProperty; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; import java.util.Optional; /** @@ -12,43 +13,32 @@ * * @author Sven Woltmann */ +@LookupIfProperty(name = "persistence", stringValue = "mysql") +@ApplicationScoped public class JpaCartRepository implements CartRepository { - private final EntityManagerFactory entityManagerFactory; + private final JpaCartPanacheRepository panacheRepository; - public JpaCartRepository(EntityManagerFactory entityManagerFactory) { - this.entityManagerFactory = entityManagerFactory; + public JpaCartRepository(JpaCartPanacheRepository panacheRepository) { + this.panacheRepository = panacheRepository; } @Override + @Transactional public void save(Cart cart) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - entityManager.getTransaction().begin(); - entityManager.merge(CartMapper.toJpaEntity(cart)); - entityManager.getTransaction().commit(); - } + panacheRepository.getEntityManager().merge(CartMapper.toJpaEntity(cart)); } @Override + @Transactional public Optional findByCustomerId(CustomerId customerId) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - CartJpaEntity cartJpaEntity = entityManager.find(CartJpaEntity.class, customerId.value()); - return CartMapper.toModelEntityOptional(cartJpaEntity); - } + CartJpaEntity cartJpaEntity = panacheRepository.findById(customerId.value()); + return CartMapper.toModelEntityOptional(cartJpaEntity); } @Override + @Transactional public void deleteByCustomerId(CustomerId customerId) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - entityManager.getTransaction().begin(); - - CartJpaEntity cartJpaEntity = entityManager.find(CartJpaEntity.class, customerId.value()); - - if (cartJpaEntity != null) { - entityManager.remove(cartJpaEntity); - } - - entityManager.getTransaction().commit(); - } + panacheRepository.deleteById(customerId.value()); } } diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductPanacheRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductPanacheRepository.java new file mode 100644 index 0000000..96d855c --- /dev/null +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductPanacheRepository.java @@ -0,0 +1,13 @@ +package eu.happycoders.shop.adapter.out.persistence.jpa; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Panache repository for {@link ProductJpaEntity}. + * + * @author Sven Woltmann + */ +@ApplicationScoped +public class JpaProductPanacheRepository + implements PanacheRepositoryBase {} diff --git a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepository.java b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepository.java index a6e2a9a..2d7eb0a 100644 --- a/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepository.java +++ b/adapter/src/main/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepository.java @@ -4,9 +4,10 @@ import eu.happycoders.shop.application.port.out.persistence.ProductRepository; import eu.happycoders.shop.model.product.Product; import eu.happycoders.shop.model.product.ProductId; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.TypedQuery; +import io.quarkus.arc.lookup.LookupIfProperty; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; import java.util.List; import java.util.Optional; @@ -15,49 +16,42 @@ * * @author Sven Woltmann */ +@LookupIfProperty(name = "persistence", stringValue = "mysql") +@ApplicationScoped public class JpaProductRepository implements ProductRepository { - private final EntityManagerFactory entityManagerFactory; + private final JpaProductPanacheRepository panacheRepository; - public JpaProductRepository(EntityManagerFactory entityManagerFactory) { - this.entityManagerFactory = entityManagerFactory; - createDemoProducts(); + public JpaProductRepository(JpaProductPanacheRepository panacheRepository) { + this.panacheRepository = panacheRepository; } - private void createDemoProducts() { + @PostConstruct + void createDemoProducts() { DemoProducts.DEMO_PRODUCTS.forEach(this::save); } @Override + @Transactional public void save(Product product) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - entityManager.getTransaction().begin(); - entityManager.merge(ProductMapper.toJpaEntity(product)); - entityManager.getTransaction().commit(); - } + panacheRepository.getEntityManager().merge(ProductMapper.toJpaEntity(product)); } @Override + @Transactional public Optional findById(ProductId productId) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - ProductJpaEntity jpaEntity = entityManager.find(ProductJpaEntity.class, productId.value()); - return ProductMapper.toModelEntityOptional(jpaEntity); - } + ProductJpaEntity jpaEntity = panacheRepository.findById(productId.value()); + return ProductMapper.toModelEntityOptional(jpaEntity); } @Override + @Transactional public List findByNameOrDescription(String queryString) { - try (EntityManager entityManager = entityManagerFactory.createEntityManager()) { - TypedQuery query = - entityManager - .createQuery( - "from ProductJpaEntity where name like :query or description like :query", - ProductJpaEntity.class) - .setParameter("query", "%" + queryString + "%"); - - List entities = query.getResultList(); - - return ProductMapper.toModelEntities(entities); - } + List entities = + panacheRepository + .find("name like ?1 or description like ?1", "%" + queryString + "%") + .list(); + + return ProductMapper.toModelEntities(entities); } } diff --git a/adapter/src/main/resources/META-INF/persistence.xml b/adapter/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index 6794017..0000000 --- a/adapter/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - eu.happycoders.shop.adapter.out.persistence.jpa.CartJpaEntity - eu.happycoders.shop.adapter.out.persistence.jpa.CartLineItemJpaEntity - eu.happycoders.shop.adapter.out.persistence.jpa.ProductJpaEntity - true - - diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/TestProfileWithMySQL.java b/adapter/src/test/java/eu/happycoders/shop/adapter/TestProfileWithMySQL.java new file mode 100644 index 0000000..c097e95 --- /dev/null +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/TestProfileWithMySQL.java @@ -0,0 +1,12 @@ +package eu.happycoders.shop.adapter; + +import io.quarkus.test.junit.QuarkusTestProfile; +import java.util.Map; + +public class TestProfileWithMySQL implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("persistence", "mysql"); + } +} diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/HttpTestCommons.java b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/HttpTestCommons.java index 2c4b51c..12a181d 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/HttpTestCommons.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/HttpTestCommons.java @@ -7,9 +7,6 @@ public final class HttpTestCommons { - // So the tests can run when the application runs on port 8080: - public static final int TEST_PORT = 8082; - private HttpTestCommons() {} public static void assertThatResponseIsError( diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/cart/CartsControllerTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/cart/CartsControllerTest.java index 7757835..2e2b48a 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/cart/CartsControllerTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/cart/CartsControllerTest.java @@ -1,6 +1,5 @@ package eu.happycoders.shop.adapter.in.rest.cart; -import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT; import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.assertThatResponseIsError; import static eu.happycoders.shop.adapter.in.rest.cart.CartsControllerAssertions.assertThatResponseIsCart; import static eu.happycoders.shop.model.money.TestMoneyFactory.euros; @@ -8,7 +7,6 @@ import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; import static jakarta.ws.rs.core.Response.Status.NO_CONTENT; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,62 +19,27 @@ import eu.happycoders.shop.model.customer.CustomerId; import eu.happycoders.shop.model.product.Product; import eu.happycoders.shop.model.product.ProductId; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; import io.restassured.response.Response; -import jakarta.ws.rs.core.Application; -import java.util.Set; -import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +@QuarkusTest class CartsControllerTest { private static final CustomerId TEST_CUSTOMER_ID = new CustomerId(61157); private static final Product TEST_PRODUCT_1 = createTestProduct(euros(19, 99)); private static final Product TEST_PRODUCT_2 = createTestProduct(euros(25, 99)); - private static final AddToCartUseCase addToCartUseCase = mock(AddToCartUseCase.class); - private static final GetCartUseCase getCartUseCase = mock(GetCartUseCase.class); - private static final EmptyCartUseCase emptyCartUseCase = mock(EmptyCartUseCase.class); - - private static UndertowJaxrsServer server; - - @BeforeAll - static void init() { - server = - new UndertowJaxrsServer() - .setPort(TEST_PORT) - .start() - .deploy( - new Application() { - @Override - public Set getSingletons() { - return Set.of( - new AddToCartController(addToCartUseCase), - new GetCartController(getCartUseCase), - new EmptyCartController(emptyCartUseCase)); - } - }); - } - - @AfterAll - static void stop() { - server.stop(); - } - - @BeforeEach - void resetMocks() { - Mockito.reset(addToCartUseCase, getCartUseCase, emptyCartUseCase); - } + @InjectMock AddToCartUseCase addToCartUseCase; + @InjectMock GetCartUseCase getCartUseCase; + @InjectMock EmptyCartUseCase emptyCartUseCase; @Test void givenASyntacticallyInvalidCustomerId_getCart_returnsAnError() { String customerId = "foo"; - Response response = - given().port(TEST_PORT).get("/carts/" + customerId).then().extract().response(); + Response response = given().get("/carts/" + customerId).then().extract().response(); assertThatResponseIsError(response, BAD_REQUEST, "Invalid 'customerId'"); } @@ -92,8 +55,7 @@ void givenAValidCustomerIdAndACart_getCart_requestsCartFromUseCaseAndReturnsIt() when(getCartUseCase.getCart(customerId)).thenReturn(cart); - Response response = - given().port(TEST_PORT).get("/carts/" + customerId.value()).then().extract().response(); + Response response = given().get("/carts/" + customerId.value()).then().extract().response(); assertThatResponseIsCart(response, cart); } @@ -112,7 +74,6 @@ void givenSomeTestData_addLineItem_invokesAddToCartUseCaseAndReturnsUpdatedCart( Response response = given() - .port(TEST_PORT) .queryParam("productId", productId.value()) .queryParam("quantity", quantity) .post("/carts/" + customerId.value() + "/line-items") @@ -131,7 +92,6 @@ void givenAnInvalidProductId_addLineItem_returnsAnError() { Response response = given() - .port(TEST_PORT) .queryParam("productId", productId) .queryParam("quantity", quantity) .post("/carts/" + customerId.value() + "/line-items") @@ -154,7 +114,6 @@ void givenProductNotFound_addLineItem_returnsAnError() Response response = given() - .port(TEST_PORT) .queryParam("productId", productId.value()) .queryParam("quantity", quantity) .post("/carts/" + customerId.value() + "/line-items") @@ -177,7 +136,6 @@ void givenNotEnoughItemsInStock_addLineItem_returnsAnError() Response response = given() - .port(TEST_PORT) .queryParam("productId", productId.value()) .queryParam("quantity", quantity) .post("/carts/" + customerId.value() + "/line-items") @@ -192,11 +150,7 @@ void givenNotEnoughItemsInStock_addLineItem_returnsAnError() void givenACustomerId_deleteCart_invokesDeleteCartUseCaseAndReturnsUpdatedCart() { CustomerId customerId = TEST_CUSTOMER_ID; - given() - .port(TEST_PORT) - .delete("/carts/" + customerId.value()) - .then() - .statusCode(NO_CONTENT.getStatusCode()); + given().delete("/carts/" + customerId.value()).then().statusCode(NO_CONTENT.getStatusCode()); verify(emptyCartUseCase).emptyCart(customerId); } diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/product/ProductsControllerTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/product/ProductsControllerTest.java index 3946482..fad9dab 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/product/ProductsControllerTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/in/rest/product/ProductsControllerTest.java @@ -1,61 +1,28 @@ package eu.happycoders.shop.adapter.in.rest.product; -import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT; import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.assertThatResponseIsError; import static eu.happycoders.shop.adapter.in.rest.product.ProductsControllerAssertions.assertThatResponseIsProductList; import static eu.happycoders.shop.model.money.TestMoneyFactory.euros; import static eu.happycoders.shop.model.product.TestProductFactory.createTestProduct; import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import eu.happycoders.shop.application.port.in.product.FindProductsUseCase; import eu.happycoders.shop.model.product.Product; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; import io.restassured.response.Response; -import jakarta.ws.rs.core.Application; import java.util.List; -import java.util.Set; -import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +@QuarkusTest class ProductsControllerTest { private static final Product TEST_PRODUCT_1 = createTestProduct(euros(19, 99)); private static final Product TEST_PRODUCT_2 = createTestProduct(euros(25, 99)); - private static final FindProductsUseCase findProductsUseCase = mock(FindProductsUseCase.class); - - private static UndertowJaxrsServer server; - - @BeforeAll - static void init() { - server = - new UndertowJaxrsServer() - .setPort(TEST_PORT) - .start() - .deploy( - new Application() { - @Override - public Set getSingletons() { - return Set.of(new FindProductsController(findProductsUseCase)); - } - }); - } - - @AfterAll - static void stop() { - server.stop(); - } - - @BeforeEach - void resetMocks() { - Mockito.reset(findProductsUseCase); - } + @InjectMock FindProductsUseCase findProductsUseCase; @Test void givenAQueryAndAListOfProducts_findProducts_requestsProductsViaQueryAndReturnsThem() { @@ -65,20 +32,14 @@ void givenAQueryAndAListOfProducts_findProducts_requestsProductsViaQueryAndRetur when(findProductsUseCase.findByNameOrDescription(query)).thenReturn(productList); Response response = - given() - .port(TEST_PORT) - .queryParam("query", query) - .get("/products") - .then() - .extract() - .response(); + given().queryParam("query", query).get("/products").then().extract().response(); assertThatResponseIsProductList(response, productList); } @Test void givenANullQuery_findProducts_returnsError() { - Response response = given().port(TEST_PORT).get("/products").then().extract().response(); + Response response = given().get("/products").then().extract().response(); assertThatResponseIsError(response, BAD_REQUEST, "Missing 'query'"); } @@ -90,13 +51,7 @@ void givenATooShortQuery_findProducts_returnsError() { .thenThrow(IllegalArgumentException.class); Response response = - given() - .port(TEST_PORT) - .queryParam("query", query) - .get("/products") - .then() - .extract() - .response(); + given().queryParam("query", query).get("/products").then().extract().response(); assertThatResponseIsError(response, BAD_REQUEST, "Invalid 'query'"); } diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractCartRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractCartRepositoryTest.java index 4cb4678..7a8a0d8 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractCartRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractCartRepositoryTest.java @@ -11,37 +11,38 @@ import eu.happycoders.shop.model.cart.NotEnoughItemsInStockException; import eu.happycoders.shop.model.customer.CustomerId; import eu.happycoders.shop.model.product.Product; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public abstract class AbstractCartRepositoryTest< - T extends CartRepository, U extends ProductRepository> { +public abstract class AbstractCartRepositoryTest { private static final Product TEST_PRODUCT_1 = createTestProduct(euros(19, 99)); private static final Product TEST_PRODUCT_2 = createTestProduct(euros(1, 49)); private static final AtomicInteger CUSTOMER_ID_SEQUENCE_GENERATOR = new AtomicInteger(); - private T cartRepository; + @Inject Instance cartRepositoryInstance; + + @Inject Instance productRepositoryInstance; + + private CartRepository cartRepository; @BeforeEach void initRepositories() { - cartRepository = createCartRepository(); + cartRepository = cartRepositoryInstance.get(); persistTestProducts(); } - protected abstract T createCartRepository(); - private void persistTestProducts() { - U productRepository = createProductRepository(); + ProductRepository productRepository = productRepositoryInstance.get(); productRepository.save(TEST_PRODUCT_1); productRepository.save(TEST_PRODUCT_2); } - protected abstract U createProductRepository(); - @Test void givenACustomerIdForWhichNoCartIsPersisted_findByCustomerId_returnsAnEmptyOptional() { CustomerId customerId = createUniqueCustomerId(); diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractProductRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractProductRepositoryTest.java index aa34d49..c8c7c39 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractProductRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/AbstractProductRepositoryTest.java @@ -5,22 +5,24 @@ import eu.happycoders.shop.application.port.out.persistence.ProductRepository; import eu.happycoders.shop.model.product.Product; import eu.happycoders.shop.model.product.ProductId; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public abstract class AbstractProductRepositoryTest { +public abstract class AbstractProductRepositoryTest { - private T productRepository; + @Inject Instance productRepositoryInstance; + + private ProductRepository productRepository; @BeforeEach void initRepository() { - productRepository = createProductRepository(); + productRepository = productRepositoryInstance.get(); } - protected abstract T createProductRepository(); - @Test void givenTestProductsAndATestProductId_findById_returnsATestProduct() { ProductId productId = DemoProducts.COMPUTER_MONITOR.id(); diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepositoryTest.java index 811b9d6..7df6d04 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryCartRepositoryTest.java @@ -1,17 +1,7 @@ package eu.happycoders.shop.adapter.out.persistence.inmemory; import eu.happycoders.shop.adapter.out.persistence.AbstractCartRepositoryTest; +import io.quarkus.test.junit.QuarkusTest; -class InMemoryCartRepositoryTest - extends AbstractCartRepositoryTest { - - @Override - protected InMemoryCartRepository createCartRepository() { - return new InMemoryCartRepository(); - } - - @Override - protected InMemoryProductRepository createProductRepository() { - return new InMemoryProductRepository(); - } -} +@QuarkusTest +class InMemoryCartRepositoryTest extends AbstractCartRepositoryTest {} diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepositoryTest.java index 52b9e07..6d42eb8 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/inmemory/InMemoryProductRepositoryTest.java @@ -1,12 +1,7 @@ package eu.happycoders.shop.adapter.out.persistence.inmemory; import eu.happycoders.shop.adapter.out.persistence.AbstractProductRepositoryTest; +import io.quarkus.test.junit.QuarkusTest; -class InMemoryProductRepositoryTest - extends AbstractProductRepositoryTest { - - @Override - protected InMemoryProductRepository createProductRepository() { - return new InMemoryProductRepository(); - } -} +@QuarkusTest +class InMemoryProductRepositoryTest extends AbstractProductRepositoryTest {} diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepositoryTest.java index b5d4aa6..4b68780 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaCartRepositoryTest.java @@ -1,41 +1,10 @@ package eu.happycoders.shop.adapter.out.persistence.jpa; +import eu.happycoders.shop.adapter.TestProfileWithMySQL; import eu.happycoders.shop.adapter.out.persistence.AbstractCartRepositoryTest; -import jakarta.persistence.EntityManagerFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.utility.DockerImageName; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; -class JpaCartRepositoryTest - extends AbstractCartRepositoryTest { - - private static MySQLContainer mysql; - private static EntityManagerFactory entityManagerFactory; - - @BeforeAll - static void startDatabase() { - mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0")); - mysql.start(); - - entityManagerFactory = - EntityManagerFactoryFactory.createMySqlEntityManagerFactory( - mysql.getJdbcUrl(), "root", "test"); - } - - @Override - protected JpaCartRepository createCartRepository() { - return new JpaCartRepository(entityManagerFactory); - } - - @Override - protected JpaProductRepository createProductRepository() { - return new JpaProductRepository(entityManagerFactory); - } - - @AfterAll - static void stopDatabase() { - entityManagerFactory.close(); - mysql.stop(); - } -} +@QuarkusTest +@TestProfile(TestProfileWithMySQL.class) +class JpaCartRepositoryTest extends AbstractCartRepositoryTest {} diff --git a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepositoryTest.java b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepositoryTest.java index 56b0f4e..bd20969 100644 --- a/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepositoryTest.java +++ b/adapter/src/test/java/eu/happycoders/shop/adapter/out/persistence/jpa/JpaProductRepositoryTest.java @@ -1,35 +1,10 @@ package eu.happycoders.shop.adapter.out.persistence.jpa; +import eu.happycoders.shop.adapter.TestProfileWithMySQL; import eu.happycoders.shop.adapter.out.persistence.AbstractProductRepositoryTest; -import jakarta.persistence.EntityManagerFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.utility.DockerImageName; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; -class JpaProductRepositoryTest extends AbstractProductRepositoryTest { - - private static MySQLContainer mysql; - private static EntityManagerFactory entityManagerFactory; - - @BeforeAll - static void startDatabase() { - mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.1")); - mysql.start(); - - entityManagerFactory = - EntityManagerFactoryFactory.createMySqlEntityManagerFactory( - mysql.getJdbcUrl(), "root", "test"); - } - - @Override - protected JpaProductRepository createProductRepository() { - return new JpaProductRepository(entityManagerFactory); - } - - @AfterAll - static void stopDatabase() { - entityManagerFactory.close(); - mysql.stop(); - } -} +@QuarkusTest +@TestProfile(TestProfileWithMySQL.class) +class JpaProductRepositoryTest extends AbstractProductRepositoryTest {} diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 3c7fade..1328237 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -32,47 +32,17 @@ ${project.version} - - - org.jboss.resteasy - resteasy-undertow - - - - org.jboss - jandex - - - - - - - mysql - mysql-connector-java - runtime - - - org.jboss.resteasy - resteasy-jackson2-provider - runtime - - - org.glassfish - jakarta.el - runtime - + - org.hibernate.orm - hibernate-core - runtime + io.quarkus + quarkus-junit5 + test - org.hibernate.validator - hibernate-validator - runtime + io.quarkus + quarkus-junit5-mockito + test - - com.tngtech.archunit archunit-junit5 @@ -95,6 +65,25 @@ + + + + io.quarkus.platform + quarkus-maven-plugin + true + + + + build + generate-code + generate-code-tests + + + + + + + test-coverage diff --git a/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/Launcher.java b/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/Launcher.java deleted file mode 100644 index b6f5c6b..0000000 --- a/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/Launcher.java +++ /dev/null @@ -1,33 +0,0 @@ -package eu.happycoders.shop.bootstrap; - -import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; - -/** - * Launcher for the application: starts the Undertow server and deploys the shop application. - * - * @author Sven Woltmann - */ -public class Launcher { - - private static final int PORT = 8080; - - private UndertowJaxrsServer server; - - public static void main(String[] args) { - new Launcher().startOnPort(PORT); - } - - public void startOnPort(int port) { - server = new UndertowJaxrsServer().setPort(port); - startServer(); - } - - private void startServer() { - server.start(); - server.deploy(RestEasyUndertowShopApplication.class); - } - - public void stop() { - server.stop(); - } -} diff --git a/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/RestEasyUndertowShopApplication.java b/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/RestEasyUndertowShopApplication.java deleted file mode 100644 index f4c716b..0000000 --- a/bootstrap/src/main/java/eu/happycoders/shop/bootstrap/RestEasyUndertowShopApplication.java +++ /dev/null @@ -1,95 +0,0 @@ -package eu.happycoders.shop.bootstrap; - -import eu.happycoders.shop.adapter.in.rest.cart.AddToCartController; -import eu.happycoders.shop.adapter.in.rest.cart.EmptyCartController; -import eu.happycoders.shop.adapter.in.rest.cart.GetCartController; -import eu.happycoders.shop.adapter.in.rest.product.FindProductsController; -import eu.happycoders.shop.adapter.out.persistence.inmemory.InMemoryCartRepository; -import eu.happycoders.shop.adapter.out.persistence.inmemory.InMemoryProductRepository; -import eu.happycoders.shop.adapter.out.persistence.jpa.EntityManagerFactoryFactory; -import eu.happycoders.shop.adapter.out.persistence.jpa.JpaCartRepository; -import eu.happycoders.shop.adapter.out.persistence.jpa.JpaProductRepository; -import eu.happycoders.shop.application.port.in.cart.AddToCartUseCase; -import eu.happycoders.shop.application.port.in.cart.EmptyCartUseCase; -import eu.happycoders.shop.application.port.in.cart.GetCartUseCase; -import eu.happycoders.shop.application.port.in.product.FindProductsUseCase; -import eu.happycoders.shop.application.port.out.persistence.CartRepository; -import eu.happycoders.shop.application.port.out.persistence.ProductRepository; -import eu.happycoders.shop.application.service.cart.AddToCartService; -import eu.happycoders.shop.application.service.cart.EmptyCartService; -import eu.happycoders.shop.application.service.cart.GetCartService; -import eu.happycoders.shop.application.service.product.FindProductsService; -import jakarta.persistence.EntityManagerFactory; -import jakarta.ws.rs.core.Application; -import java.util.Set; - -/** - * The application configuration for the Undertow server. Evaluates the persistence configuration, - * instantiates the appropriate adapters and use cases, and wires them. - * - * @author Sven Woltmann - */ -public class RestEasyUndertowShopApplication extends Application { - - private CartRepository cartRepository; - private ProductRepository productRepository; - - // We're encouraged to use "automatic discovery of resources", but I want to define them manually. - @SuppressWarnings("deprecation") - @Override - public Set getSingletons() { - initPersistenceAdapters(); - return Set.of( - addToCartController(), - getCartController(), - emptyCartController(), - findProductsController()); - } - - private void initPersistenceAdapters() { - String persistence = System.getProperty("persistence", "inmemory"); - switch (persistence) { - case "inmemory" -> initInMemoryAdapters(); - case "mysql" -> initMySqlAdapters(); - default -> throw new IllegalArgumentException( - "Invalid 'persistence' property: '%s' (allowed: 'inmemory', 'mysql')" - .formatted(persistence)); - } - } - - private void initInMemoryAdapters() { - cartRepository = new InMemoryCartRepository(); - productRepository = new InMemoryProductRepository(); - } - - // The EntityManagerFactory doesn't need to get closed before the application is stopped - @SuppressWarnings("PMD.CloseResource") - private void initMySqlAdapters() { - EntityManagerFactory entityManagerFactory = - EntityManagerFactoryFactory.createMySqlEntityManagerFactory( - "jdbc:mysql://localhost:3306/shop", "root", "test"); - - cartRepository = new JpaCartRepository(entityManagerFactory); - productRepository = new JpaProductRepository(entityManagerFactory); - } - - private AddToCartController addToCartController() { - AddToCartUseCase addToCartUseCase = new AddToCartService(cartRepository, productRepository); - return new AddToCartController(addToCartUseCase); - } - - private GetCartController getCartController() { - GetCartUseCase getCartUseCase = new GetCartService(cartRepository); - return new GetCartController(getCartUseCase); - } - - private EmptyCartController emptyCartController() { - EmptyCartUseCase emptyCartUseCase = new EmptyCartService(cartRepository); - return new EmptyCartController(emptyCartUseCase); - } - - private FindProductsController findProductsController() { - FindProductsUseCase findProductsUseCase = new FindProductsService(productRepository); - return new FindProductsController(findProductsUseCase); - } -} diff --git a/bootstrap/src/main/resources/application.properties b/bootstrap/src/main/resources/application.properties new file mode 100644 index 0000000..bdb9f17 --- /dev/null +++ b/bootstrap/src/main/resources/application.properties @@ -0,0 +1,11 @@ +# Without this line, Quarkus would abort with the following error message: +# "Model classes are defined for the default persistence unit but configured datasource not found: +# the default EntityManagerFactory will not be created." +%prod.quarkus.datasource.jdbc.url=dummy +%prod.persistence=inmemory + +%mysql.quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/shop +%mysql.quarkus.datasource.username=root +%mysql.quarkus.datasource.password=test +%mysql.quarkus.hibernate-orm.database.generation=update +%mysql.persistence=mysql diff --git a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/CartTest.java b/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/CartTest.java index 1aca021..ba7657f 100644 --- a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/CartTest.java +++ b/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/CartTest.java @@ -1,23 +1,27 @@ package eu.happycoders.shop.bootstrap.e2e; -import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT; import static eu.happycoders.shop.adapter.in.rest.cart.CartsControllerAssertions.assertThatResponseIsCart; import static eu.happycoders.shop.adapter.out.persistence.DemoProducts.LED_LIGHTS; import static eu.happycoders.shop.adapter.out.persistence.DemoProducts.MONITOR_DESK_MOUNT; import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.NO_CONTENT; +import eu.happycoders.shop.adapter.TestProfileWithMySQL; import eu.happycoders.shop.model.cart.Cart; import eu.happycoders.shop.model.cart.NotEnoughItemsInStockException; import eu.happycoders.shop.model.customer.CustomerId; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.restassured.response.Response; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +@QuarkusTest +@TestProfile(TestProfileWithMySQL.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class CartTest extends EndToEndTest { +class CartTest { private static final CustomerId TEST_CUSTOMER_ID = new CustomerId(61157); private static final String CARTS_PATH = "/carts/" + TEST_CUSTOMER_ID.value(); @@ -28,7 +32,6 @@ void givenAnEmptyCart_addLineItem_addsTheLineItemAndReturnsTheCartWithTheAddedIt throws NotEnoughItemsInStockException { Response response = given() - .port(TEST_PORT) .queryParam("productId", LED_LIGHTS.id().value()) .queryParam("quantity", 3) .post(CARTS_PATH + "/line-items") @@ -48,7 +51,6 @@ void givenACartWithOneLineItem_addLineItem_addsTheLineItemAndReturnsACartWithTwo throws NotEnoughItemsInStockException { Response response = given() - .port(TEST_PORT) .queryParam("productId", MONITOR_DESK_MOUNT.id().value()) .queryParam("quantity", 1) .post(CARTS_PATH + "/line-items") @@ -66,7 +68,7 @@ void givenACartWithOneLineItem_addLineItem_addsTheLineItemAndReturnsACartWithTwo @Test @Order(3) void givenACartWithTwoLineItems_getCart_returnsTheCart() throws NotEnoughItemsInStockException { - Response response = given().port(TEST_PORT).get(CARTS_PATH).then().extract().response(); + Response response = given().get(CARTS_PATH).then().extract().response(); Cart expectedCart = new Cart(TEST_CUSTOMER_ID); expectedCart.addProduct(LED_LIGHTS, 3); @@ -78,13 +80,13 @@ void givenACartWithTwoLineItems_getCart_returnsTheCart() throws NotEnoughItemsIn @Test @Order(4) void givenACartWithTwoLineItems_delete_returnsStatusCodeNoContent() { - given().port(TEST_PORT).delete(CARTS_PATH).then().statusCode(NO_CONTENT.getStatusCode()); + given().delete(CARTS_PATH).then().statusCode(NO_CONTENT.getStatusCode()); } @Test @Order(5) void givenAnEmptiedCart_getCart_returnsAnEmptyCart() { - Response response = given().port(TEST_PORT).get(CARTS_PATH).then().extract().response(); + Response response = given().get(CARTS_PATH).then().extract().response(); assertThatResponseIsCart(response, new Cart(TEST_CUSTOMER_ID)); } diff --git a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/EndToEndTest.java b/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/EndToEndTest.java deleted file mode 100644 index 666d729..0000000 --- a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/EndToEndTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package eu.happycoders.shop.bootstrap.e2e; - -import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT; - -import eu.happycoders.shop.bootstrap.Launcher; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; - -abstract class EndToEndTest { - - private static Launcher launcher; - - @BeforeAll - static void init() { - launcher = new Launcher(); - launcher.startOnPort(TEST_PORT); - } - - @AfterAll - static void stop() { - launcher.stop(); - } -} diff --git a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/FindProductsTest.java b/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/FindProductsTest.java index 76fb4c9..2410dea 100644 --- a/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/FindProductsTest.java +++ b/bootstrap/src/test/java/eu/happycoders/shop/bootstrap/e2e/FindProductsTest.java @@ -1,29 +1,27 @@ package eu.happycoders.shop.bootstrap.e2e; -import static eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT; import static eu.happycoders.shop.adapter.in.rest.product.ProductsControllerAssertions.assertThatResponseIsProductList; import static eu.happycoders.shop.adapter.out.persistence.DemoProducts.COMPUTER_MONITOR; import static eu.happycoders.shop.adapter.out.persistence.DemoProducts.MONITOR_DESK_MOUNT; import static io.restassured.RestAssured.given; +import eu.happycoders.shop.adapter.TestProfileWithMySQL; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.restassured.response.Response; import java.util.List; import org.junit.jupiter.api.Test; -class FindProductsTest extends EndToEndTest { +@QuarkusTest +@TestProfile(TestProfileWithMySQL.class) +class FindProductsTest { @Test void givenTestProductsAndAQuery_findProducts_returnsMatchingProducts() { String query = "monitor"; Response response = - given() - .port(TEST_PORT) - .queryParam("query", query) - .get("/products") - .then() - .extract() - .response(); + given().queryParam("query", query).get("/products").then().extract().response(); assertThatResponseIsProductList(response, List.of(COMPUTER_MONITOR, MONITOR_DESK_MOUNT)); } diff --git a/pom.xml b/pom.xml index d43685b..57c951a 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ 20 UTF-8 + 3.4.3 6.55.0 @@ -49,7 +50,7 @@ org.junit.jupiter junit-jupiter - 5.10.0 + test @@ -61,73 +62,38 @@ org.mockito mockito-core - 5.5.0 + test + + + io.quarkus.platform + quarkus-bom + ${quarkus.platform.version} + pom + import + + com.tngtech.archunit archunit-junit5 1.1.0 - - mysql - mysql-connector-java - 8.0.33 - - - io.rest-assured - rest-assured - 5.3.2 - - - jakarta.persistence - jakarta.persistence-api - 3.1.0 - - - jakarta.ws.rs - jakarta.ws.rs-api - 3.1.0 - - - org.glassfish - jakarta.el - 5.0.0-M1 - - - org.hibernate.orm - hibernate-core - 6.3.1.Final - - - org.hibernate.validator - hibernate-validator - 8.0.1.Final - - - org.jboss.resteasy - resteasy-undertow - 6.2.5.Final - - - org.jboss.resteasy - resteasy-jackson2-provider - 6.2.5.Final - - - org.testcontainers - mysql - 1.19.0 - + + io.quarkus.platform + quarkus-maven-plugin + ${quarkus.platform.version} + +