diff --git a/.gitignore b/.gitignore index 87b9545954..1a639306f0 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,8 @@ ethereum/logs/ jmeter/src/main/resources/*-JMeter.csv jmeter/src/main/resources/*-Basic*.csv jmeter/src/main/resources/*-JMeter*.csv +jmeter/src/main/resources/*ReportsDashboard*.csv +jmeter/src/main/resources/dashboard/*ReportsDashboard*.csv ninja/devDb.mv.db diff --git a/apache-cxf-modules/cxf-aegis/src/test/java/com/baeldung/cxf/aegis/BaeldungIntegrationTest.java b/apache-cxf-modules/cxf-aegis/src/test/java/com/baeldung/cxf/aegis/BaeldungIntegrationTest.java index b28b987cfa..9c89d769e0 100644 --- a/apache-cxf-modules/cxf-aegis/src/test/java/com/baeldung/cxf/aegis/BaeldungIntegrationTest.java +++ b/apache-cxf-modules/cxf-aegis/src/test/java/com/baeldung/cxf/aegis/BaeldungIntegrationTest.java @@ -80,16 +80,20 @@ public class BaeldungIntegrationTest { private void marshalCourseRepo(CourseRepo courseRepo) throws Exception { AegisWriter writer = context.createXMLStreamWriter(); AegisType aegisType = context.getTypeMapping().getType(CourseRepo.class); - XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(new FileOutputStream(fileName)); + final FileOutputStream stream = new FileOutputStream(fileName); + XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(stream); writer.write(courseRepo, new QName("http://aegis.cxf.baeldung.com", "baeldung"), false, xmlWriter, aegisType); xmlWriter.close(); + stream.close(); } private CourseRepo unmarshalCourseRepo() throws Exception { AegisReader reader = context.createXMLStreamReader(); - XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName)); + final FileInputStream stream = new FileInputStream(fileName); + XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(stream); CourseRepo courseRepo = (CourseRepo) reader.read(xmlReader, context.getTypeMapping().getType(CourseRepo.class)); xmlReader.close(); + stream.close(); return courseRepo; } @@ -97,7 +101,7 @@ public class BaeldungIntegrationTest { public void cleanup(){ File testFile = new File(fileName); if (testFile.exists()) { - testFile.delete(); + testFile.deleteOnExit(); } } } \ No newline at end of file diff --git a/apache-velocity/pom.xml b/apache-velocity/pom.xml index f4b6de8872..a562ebeec0 100644 --- a/apache-velocity/pom.xml +++ b/apache-velocity/pom.xml @@ -63,6 +63,7 @@ 4.5.2 1.7 2.0 + 3.3.2 \ No newline at end of file diff --git a/asciidoctor/pom.xml b/asciidoctor/pom.xml index e24917f2f4..b72f050379 100644 --- a/asciidoctor/pom.xml +++ b/asciidoctor/pom.xml @@ -62,10 +62,10 @@ - 1.5.6 - 1.5.6 - 1.5.0-alpha.15 - 1.5.0-alpha.15 + 2.2.2 + 2.5.7 + 2.3.4 + 2.3.4 \ No newline at end of file diff --git a/aws-modules/aws-reactive/pom.xml b/aws-modules/aws-reactive/pom.xml index fbad5e30d0..e6b50cadb2 100644 --- a/aws-modules/aws-reactive/pom.xml +++ b/aws-modules/aws-reactive/pom.xml @@ -77,6 +77,7 @@ org.projectlombok lombok + ${lombok.version} @@ -92,6 +93,7 @@ 2.2.1.RELEASE 2.17.283 + 1.18.20 \ No newline at end of file diff --git a/core-java-modules/core-java-8-2/src/main/java/com/baeldung/argsVsvarargs/StringArrayAndVarargs.java b/core-java-modules/core-java-8-2/src/main/java/com/baeldung/argsVsvarargs/StringArrayAndVarargs.java new file mode 100644 index 0000000000..dc9137646f --- /dev/null +++ b/core-java-modules/core-java-8-2/src/main/java/com/baeldung/argsVsvarargs/StringArrayAndVarargs.java @@ -0,0 +1,19 @@ +package com.baeldung.argsVsvarargs; + +public class StringArrayAndVarargs { + public static void capitalizeNames(String[] args) { + for(int i = 0; i < args.length; i++){ + args[i] = args[i].toUpperCase(); + } + + } + + public static String[] firstLetterOfWords(String... args) { + String[] firstLetter = new String[args.length]; + for(int i = 0; i < args.length; i++){ + firstLetter[i] = String.valueOf(args[i].charAt(0)); + } + return firstLetter; + } + +} diff --git a/core-java-modules/core-java-8-2/src/test/java/com/baeldung/argsVsvarargs/StringArrayAndVarargsUnitTest.java b/core-java-modules/core-java-8-2/src/test/java/com/baeldung/argsVsvarargs/StringArrayAndVarargsUnitTest.java new file mode 100644 index 0000000000..f917373229 --- /dev/null +++ b/core-java-modules/core-java-8-2/src/test/java/com/baeldung/argsVsvarargs/StringArrayAndVarargsUnitTest.java @@ -0,0 +1,24 @@ +package com.baeldung.argsVsvarargs; + +import static com.baeldung.argsVsvarargs.StringArrayAndVarargs.capitalizeNames; +import static com.baeldung.argsVsvarargs.StringArrayAndVarargs.firstLetterOfWords; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class StringArrayAndVarargsUnitTest { + + @Test + void whenCheckingArgumentClassName_thenNameShouldBeStringArray() { + String[] names = {"john", "ade", "kofi", "imo"}; + assertNotNull(names); + assertEquals("java.lang.String[]", names.getClass().getTypeName()); + capitalizeNames(names); + } + + @Test + void whenCheckingReturnedObjectClass_thenClassShouldBeStringArray() { + assertEquals(String[].class, firstLetterOfWords("football", "basketball", "volleyball").getClass()); + assertEquals(3, firstLetterOfWords("football", "basketball", "volleyball").length); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-arrays-operations-basic/src/test/java/com/baeldung/array/isobjectarray/CheckObjectIsArrayUnitTest.java b/core-java-modules/core-java-arrays-operations-basic/src/test/java/com/baeldung/array/isobjectarray/CheckObjectIsArrayUnitTest.java new file mode 100644 index 0000000000..ed38ab0765 --- /dev/null +++ b/core-java-modules/core-java-arrays-operations-basic/src/test/java/com/baeldung/array/isobjectarray/CheckObjectIsArrayUnitTest.java @@ -0,0 +1,78 @@ +package com.baeldung.array.isobjectarray; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Array; + +import org.junit.jupiter.api.Test; + +public class CheckObjectIsArrayUnitTest { + private static final Object ARRAY_INT = new int[] { 1, 2, 3, 4, 5 }; + private static final Object ARRAY_PERSON = new Person[] { new Person("Jackie Chan", "Hong Kong"), new Person("Tom Hanks", "United States") }; + + boolean isArray(Object obj) { + return obj instanceof Object[] || obj instanceof boolean[] || obj instanceof byte[] || obj instanceof short[] || obj instanceof char[] || obj instanceof int[] || obj instanceof long[] || obj instanceof float[] || obj instanceof double[]; + } + + @Test + void givenAnArrayObject_whenUsingInstanceof_getExpectedResult() { + assertTrue(ARRAY_PERSON instanceof Object[]); + assertFalse(ARRAY_INT instanceof Object[]); + assertTrue(ARRAY_INT instanceof int[]); + } + + @Test + void givenAnArrayObject_whenUsingOurIsArray_getExpectedResult() { + assertTrue(isArray(ARRAY_PERSON)); + assertTrue(isArray(ARRAY_INT)); + } + + @Test + void givenAnArrayObject_whenUsingClassIsArray_getExpectedResult() { + assertTrue(ARRAY_INT.getClass() + .isArray()); + assertTrue(ARRAY_PERSON.getClass() + .isArray()); + + assertEquals(Person.class, ARRAY_PERSON.getClass() + .getComponentType()); + assertEquals(int.class, ARRAY_INT.getClass() + .getComponentType()); + + } + + @Test + void givenAnArrayObject_whenUsingArrayGet_getExpectedElement() { + if (ARRAY_PERSON.getClass() + .isArray() && ARRAY_PERSON.getClass() + .getComponentType() == Person.class) { + Person person = (Person) Array.get(ARRAY_PERSON, 1); + assertEquals("Tom Hanks", person.getName()); + } + if (ARRAY_INT.getClass() + .isArray() && ARRAY_INT.getClass() + .getComponentType() == int.class) { + assertEquals(2, ((int) Array.get(ARRAY_INT, 1))); + } + } +} + +class Person { + private String name; + private String Location; + + public Person(String name, String location) { + this.name = name; + this.Location = location; + } + + public String getName() { + return name; + } + + public String getLocation() { + return Location; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithLock.java b/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithLock.java index ef6b7ee8c8..09dfe575e6 100644 --- a/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithLock.java +++ b/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithLock.java @@ -1,7 +1,7 @@ package com.baeldung.concurrent.atomic; public class SafeCounterWithLock { - private volatile int counter; + private int counter; int getValue() { return counter; diff --git a/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithoutLock.java b/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithoutLock.java index 8b2aebba7c..e1117dd842 100644 --- a/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithoutLock.java +++ b/core-java-modules/core-java-concurrency-advanced/src/main/java/com/baeldung/concurrent/atomic/SafeCounterWithoutLock.java @@ -10,12 +10,6 @@ public class SafeCounterWithoutLock { } void increment() { - while(true) { - int existingValue = getValue(); - int newValue = existingValue + 1; - if(counter.compareAndSet(existingValue, newValue)) { - return; - } - } + counter.incrementAndGet(); } } diff --git a/core-java-modules/core-java-concurrency-advanced/src/test/java/com/baeldung/concurrent/atomic/ThreadSafeCounterIntegrationTest.java b/core-java-modules/core-java-concurrency-advanced/src/test/java/com/baeldung/concurrent/atomic/ThreadSafeCounterIntegrationTest.java index c3c44b40cf..9b4b628d0f 100644 --- a/core-java-modules/core-java-concurrency-advanced/src/test/java/com/baeldung/concurrent/atomic/ThreadSafeCounterIntegrationTest.java +++ b/core-java-modules/core-java-concurrency-advanced/src/test/java/com/baeldung/concurrent/atomic/ThreadSafeCounterIntegrationTest.java @@ -17,8 +17,8 @@ public class ThreadSafeCounterIntegrationTest { SafeCounterWithLock safeCounter = new SafeCounterWithLock(); IntStream.range(0, 1000) - .forEach(count -> service.submit(safeCounter::increment)); - service.awaitTermination(100, TimeUnit.MILLISECONDS); + .forEach(count -> service.execute(safeCounter::increment)); + shutdownAndAwaitTermination(service); assertEquals(1000, safeCounter.getValue()); } @@ -29,10 +29,30 @@ public class ThreadSafeCounterIntegrationTest { SafeCounterWithoutLock safeCounter = new SafeCounterWithoutLock(); IntStream.range(0, 1000) - .forEach(count -> service.submit(safeCounter::increment)); - service.awaitTermination(100, TimeUnit.MILLISECONDS); + .forEach(count -> service.execute(safeCounter::increment)); + shutdownAndAwaitTermination(service); assertEquals(1000, safeCounter.getValue()); } + + private void shutdownAndAwaitTermination(ExecutorService pool) { + // Disable new tasks from being submitted + pool.shutdown(); + try { + // Wait a while for existing tasks to terminate + if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS)) { + // Cancel currently executing tasks forcefully + pool.shutdownNow(); + // Wait a while for tasks to respond to being cancelled + if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS)) + System.err.println("Pool did not terminate"); + } + } catch (InterruptedException ex) { + // (Re-)Cancel if current thread also interrupted + pool.shutdownNow(); + // Preserve interrupt status + Thread.currentThread().interrupt(); + } + } } diff --git a/core-java-modules/core-java-hex/README.md b/core-java-modules/core-java-hex/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core-java-modules/core-java-hex/pom.xml b/core-java-modules/core-java-hex/pom.xml new file mode 100644 index 0000000000..afac1c4d66 --- /dev/null +++ b/core-java-modules/core-java-hex/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + core-java-hex + 0.1.0-SNAPSHOT + core-java-hex + jar + + + com.baeldung.core-java-modules + core-java-modules + 0.0.1-SNAPSHOT + + + + + + + + + + + \ No newline at end of file diff --git a/core-java-modules/core-java-hex/src/test/java/com/baeldung/hextorgb/HexToRgbUnitTest.java b/core-java-modules/core-java-hex/src/test/java/com/baeldung/hextorgb/HexToRgbUnitTest.java new file mode 100644 index 0000000000..35e5b87d29 --- /dev/null +++ b/core-java-modules/core-java-hex/src/test/java/com/baeldung/hextorgb/HexToRgbUnitTest.java @@ -0,0 +1,25 @@ +package com.baeldung.hextorgb; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class HexToRgbUnitTest { + + @Test + public void givenHexCode_whenConvertedToRgb_thenCorrectRgbValuesAreReturned() { + String hexCode = "FF9933"; + int red = 255; + int green = 153; + int blue = 51; + + int resultRed = Integer.valueOf(hexCode.substring(0, 2), 16); + int resultGreen = Integer.valueOf(hexCode.substring(2, 4), 16); + int resultBlue = Integer.valueOf(hexCode.substring(4, 6), 16); + + assertEquals(red, resultRed); + assertEquals(green, resultGreen); + assertEquals(blue, resultBlue); + } + +} diff --git a/core-java-modules/core-java-string-algorithms-3/src/main/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnInteger.java b/core-java-modules/core-java-string-algorithms-3/src/main/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnInteger.java new file mode 100644 index 0000000000..ca86208e59 --- /dev/null +++ b/core-java-modules/core-java-string-algorithms-3/src/main/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnInteger.java @@ -0,0 +1,18 @@ +package com.baeldung.firstocurrenceofaninteger; + +public class FirstOccurrenceOfAnInteger { + + static Integer findFirstInteger(String s) { + int i = 0; + while (i < s.length() && !Character.isDigit(s.charAt(i))) { + i++; + } + int j = i; + while (j < s.length() && Character.isDigit(s.charAt(j))) { + j++; + } + return Integer.parseInt(s.substring(i, j)); + } + +} + diff --git a/core-java-modules/core-java-string-algorithms-3/src/test/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnIntegerUnitTest.java b/core-java-modules/core-java-string-algorithms-3/src/test/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnIntegerUnitTest.java new file mode 100644 index 0000000000..ab40316708 --- /dev/null +++ b/core-java-modules/core-java-string-algorithms-3/src/test/java/com/baeldung/firstocurrenceofaninteger/FirstOccurrenceOfAnIntegerUnitTest.java @@ -0,0 +1,46 @@ +package com.baeldung.firstocurrenceofaninteger; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +class FirstOccurrenceOfAnIntegerUnitTest { + + @Test + void whenUsingPatternMatcher_findFirstInteger() { + String s = "ba31dung123"; + Matcher matcher = Pattern.compile("\\d+").matcher(s); + matcher.find(); + int i = Integer.parseInt(matcher.group()); + Assertions.assertEquals(31, i); + } + + @Test + void whenUsingScanner_findFirstInteger() { + int i = new Scanner("ba31dung123").useDelimiter("\\D+").nextInt(); + Assertions.assertEquals(31, i); + } + + @Test + void whenUsingSplit_findFirstInteger() { + String str = "ba31dung123"; + List tokens = Arrays.stream(str.split("\\D+")) + .filter(s -> s.length() > 0).collect(Collectors.toList()); + Assertions.assertEquals(31, Integer.parseInt(tokens.get(0))); + } + + @Test + void whenUsingCustomMethod_findFirstInteger() { + String str = "ba31dung123"; + Integer i = FirstOccurrenceOfAnInteger.findFirstInteger(str); + Assertions.assertEquals(31, i); + } + + +} diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index bbbca6adf5..612e607a38 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -62,6 +62,7 @@ core-java-exceptions-4 core-java-function core-java-functional + core-java-hex core-java-io core-java-io-2 core-java-io-3 diff --git a/di-modules/guice/pom.xml b/di-modules/guice/pom.xml index cbadf2119d..a28dbe5297 100644 --- a/di-modules/guice/pom.xml +++ b/di-modules/guice/pom.xml @@ -23,7 +23,7 @@ - 4.1.0 + 5.0.1 \ No newline at end of file diff --git a/jmeter/pom.xml b/jmeter/pom.xml index c4d09b9106..acd823b74f 100644 --- a/jmeter/pom.xml +++ b/jmeter/pom.xml @@ -136,7 +136,7 @@ ${project.basedir}/src/main/resources/dashboard true true - false + true diff --git a/persistence-modules/rethinkdb/pom.xml b/persistence-modules/rethinkdb/pom.xml new file mode 100644 index 0000000000..17c7036ed3 --- /dev/null +++ b/persistence-modules/rethinkdb/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + rethinkdb + rethinkdb + Code snippets for RethinkDB articles + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + com.rethinkdb + rethinkdb-driver + 2.4.4 + + + + org.projectlombok + lombok + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 17 + + + diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/InsertIntegrationTest.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/InsertIntegrationTest.java new file mode 100644 index 0000000000..244959d854 --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/InsertIntegrationTest.java @@ -0,0 +1,62 @@ +package com.baeldung.rethinkdb; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static com.rethinkdb.RethinkDB.r; + +/** + * Some tests demonstrating inserting data. + */ +public class InsertIntegrationTest extends TestBase { + /** + * Create a table for the tests. + */ + @BeforeEach + public void createTable() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + } + + /** + * Insert a single simple record into the database. + */ + @Test + public void insertSimpleRecord() { + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("name", "Baeldung") + ) + .run(conn); + } + + @Test + public void insertMap() { + r.db(DB_NAME).table(tableName) + .insert( + Map.of("name", "Baeldung") + ) + .run(conn); + } + + @Test + public void insertComplex() { + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("name", "Baeldung") + .with("articles", r.array( + r.hashMap() + .with("name", "String Interpolation in Java") + .with("url", "https://www.baeldung.com/java-string-interpolation"), + r.hashMap() + .with("name", "Access HTTPS REST Service Using Spring RestTemplate") + .with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service") + ) + ) + ) + .run(conn); + } +} diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/QueryIntegrationTest.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/QueryIntegrationTest.java new file mode 100644 index 0000000000..263dda9bc6 --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/QueryIntegrationTest.java @@ -0,0 +1,81 @@ +package com.baeldung.rethinkdb; + +import com.rethinkdb.net.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.rethinkdb.RethinkDB.r; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Some tests demonstrating querying data. + */ +public class QueryIntegrationTest extends TestBase { + /** + * Create a table for the tests. + */ + @BeforeEach + public void createTable() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("id", "article1") + .with("name", "String Interpolation in Java") + .with("url", "https://www.baeldung.com/java-string-interpolation") + ).run(conn); + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("id", "article2") + .with("name", "Access HTTPS REST Service Using Spring RestTemplate") + .with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service") + ).run(conn); + } + + @Test + public void listAll() { + Result results = r.db(DB_NAME).table(tableName).run(conn, Map.class); + + // We can't ensure the order the results come back in. + Set expected = new HashSet<>(Set.of( + "String Interpolation in Java", + "Access HTTPS REST Service Using Spring RestTemplate" + )); + + for (Map result : results) { + assertTrue(expected.remove(result.get("name"))); + } + + assertTrue(expected.isEmpty()); + } + + @Test + public void listSome() { + Result results = r.db(DB_NAME) + .table(tableName) + .filter(r -> r.g("name").eq("String Interpolation in Java")) + .run(conn, Map.class); + + // We can't ensure the order the results come back in. + Set expected = Set.of("https://www.baeldung.com/java-string-interpolation"); + + assertEquals(expected, results.stream() + .map(r -> r.get("url")) + .collect(Collectors.toSet())); + } + + @Test + public void getByKey() { + Result results = r.db(DB_NAME).table(tableName).get("article1").run(conn, Map.class); + + assertEquals("String Interpolation in Java", results.first().get("name")); + } +} diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/StreamingIntegrationTest.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/StreamingIntegrationTest.java new file mode 100644 index 0000000000..4ca147cf68 --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/StreamingIntegrationTest.java @@ -0,0 +1,117 @@ +package com.baeldung.rethinkdb; + +import com.rethinkdb.net.Result; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static com.rethinkdb.RethinkDB.r; + +/** + * Some tests demonstrating streaming live changes to data. + */ +public class StreamingIntegrationTest extends TestBase { + @Test + public void getLiveInserts() throws InterruptedException { + ExecutorService executorService = Executors.newCachedThreadPool(); + + r.db(DB_NAME).tableCreate(tableName).run(conn); + + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn); + + executorService.submit(() -> { + Result cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class); + + cursor.stream().forEach(record -> System.out.println("Record: " + record)); + }); + + for (int i = 0; i < 10; ++i) { + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn); + TimeUnit.MILLISECONDS.sleep(100); + } + + executorService.shutdownNow(); + } + + @Test + public void getSomeLiveInserts() throws InterruptedException { + ExecutorService executorService = Executors.newCachedThreadPool(); + + r.db(DB_NAME).tableCreate(tableName).run(conn); + + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn); + + executorService.submit(() -> { + Result cursor = r.db(DB_NAME).table(tableName) + .filter(r -> r.g("index").eq(5)) + .changes() + .run(conn, Map.class); + + cursor.stream().forEach(record -> System.out.println("Record: " + record)); + }); + + for (int i = 0; i < 10; ++i) { + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn); + TimeUnit.MILLISECONDS.sleep(100); + } + + executorService.shutdownNow(); + } + + @Test + public void getLiveUpdates() throws InterruptedException { + ExecutorService executorService = Executors.newCachedThreadPool(); + + r.db(DB_NAME).tableCreate(tableName).run(conn); + + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn); + + executorService.submit(() -> { + Result cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class); + + cursor.stream().forEach(record -> System.out.println("Record: " + record)); + }); + + for (int i = 0; i < 10; ++i) { + r.db(DB_NAME).table(tableName).update(r.hashMap().with("index", i)).run(conn); + TimeUnit.MILLISECONDS.sleep(100); + } + + executorService.shutdownNow(); + } + + @Test + public void getLiveDeletes() throws InterruptedException { + ExecutorService executorService = Executors.newCachedThreadPool(); + + r.db(DB_NAME).tableCreate(tableName).run(conn); + + for (int i = 0; i < 10; ++i) { + r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn); + } + + executorService.submit(() -> { + Result cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class); + + cursor.stream().forEach(record -> System.out.println("Record: " + record)); + }); + + r.db(DB_NAME).table(tableName) + .filter(r -> r.g("index").eq(1)) + .delete() + .run(conn); + r.db(DB_NAME).table(tableName) + .filter(r -> r.g("index").eq(3)) + .delete() + .run(conn); + r.db(DB_NAME).table(tableName) + .filter(r -> r.g("index").eq(5)) + .delete() + .run(conn); + + executorService.shutdownNow(); + } +} diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TablesIntegrationTest.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TablesIntegrationTest.java new file mode 100644 index 0000000000..d60e500373 --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TablesIntegrationTest.java @@ -0,0 +1,39 @@ +package com.baeldung.rethinkdb; + +import com.rethinkdb.gen.exc.ReqlOpFailedError; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.rethinkdb.RethinkDB.r; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Some tests demonstrating working with tables. + */ +public class TablesIntegrationTest extends TestBase { + + @Test + public void createTable() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + } + + @Test + public void createTableTwice() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + + Assertions.assertThrows(ReqlOpFailedError.class, () -> { + r.db(DB_NAME).tableCreate(tableName).run(conn); + }); + } + + @Test + public void listTables() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + + List tables = r.db(DB_NAME).tableList().run(conn, List.class).first(); + + assertTrue(tables.contains(tableName)); + } +} diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TestBase.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TestBase.java new file mode 100644 index 0000000000..396f6655ea --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/TestBase.java @@ -0,0 +1,44 @@ +package com.baeldung.rethinkdb; + +import com.rethinkdb.net.Connection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.util.UUID; + +import static com.rethinkdb.RethinkDB.r; + +/** + * Base class for RethinkDB tests. + */ +public class TestBase { + /** The database name to work with */ + protected static final String DB_NAME = "test"; + + /** A randomly generated table name so they never collide */ + protected final String tableName = UUID.randomUUID().toString().replaceAll("-",""); + + /** A database connection */ + protected Connection conn; + + /** + * Connect to the database for each test + */ + @BeforeEach + public void connect() { + conn = r.connection() + .hostname("localhost") + .port(28015) + .connect(); + } + + /** + * Disconnect from the database after each test + */ + @AfterEach + public void disconnect() { + if (this.conn != null) { + conn.close(); + } + } +} diff --git a/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/UpdateIntegrationTest.java b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/UpdateIntegrationTest.java new file mode 100644 index 0000000000..39fad3a878 --- /dev/null +++ b/persistence-modules/rethinkdb/src/test/java/com/baeldung/rethinkdb/UpdateIntegrationTest.java @@ -0,0 +1,55 @@ +package com.baeldung.rethinkdb; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.rethinkdb.RethinkDB.r; + +/** + * Some tests demonstrating updating data. + */ +public class UpdateIntegrationTest extends TestBase { + /** + * Create a table for the tests. + */ + @BeforeEach + public void createTable() { + r.db(DB_NAME).tableCreate(tableName).run(conn); + + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("id", "article1") + .with("name", "String Interpolation in Java") + .with("url", "https://www.baeldung.com/java-string-interpolation") + ).run(conn); + r.db(DB_NAME).table(tableName) + .insert( + r.hashMap() + .with("id", "article2") + .with("name", "Access HTTPS REST Service Using Spring RestTemplate") + .with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service") + ).run(conn); + } + + @Test + public void updateAll() { + r.db(DB_NAME).table(tableName).update(r.hashMap().with("site", "Baeldung")).run(conn); + } + + @Test + public void updateSome() { + r.db(DB_NAME).table(tableName) + .filter(r -> r.g("name").eq("String Interpolation in Java")) + .update(r.hashMap().with("category", "java")) + .run(conn); + } + + @Test + public void delete() { + r.db(DB_NAME).table(tableName) + .filter(r -> r.g("name").eq("String Interpolation in Java")) + .delete() + .run(conn); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java index 1495822bc0..0ff97eb6c1 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java @@ -4,8 +4,6 @@ import org.bson.BsonBinary; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; -import com.mongodb.client.vault.ClientEncryption; - @Configuration public class EncryptionConfig { @@ -21,18 +19,8 @@ public class EncryptionConfig { @Value("${com.baeldung.csfle.auto-decryption:false}") private Boolean autoDecryption; - private ClientEncryption encryption; - private BsonBinary dataKeyId; - public void setEncryption(ClientEncryption encryption) { - this.encryption = encryption; - } - - public ClientEncryption getEncryption() { - return encryption; - } - public void setDataKeyId(BsonBinary dataKeyId) { this.dataKeyId = dataKeyId; } diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java index 29076f4e61..e63034a5b5 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java @@ -11,12 +11,12 @@ import org.bson.Document; import org.bson.conversions.Bson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; -import com.baeldung.boot.csfle.config.converter.IntegerConverter; -import com.baeldung.boot.csfle.config.converter.StringConverter; +import com.baeldung.boot.csfle.config.converter.BinaryConverter; import com.mongodb.AutoEncryptionSettings; import com.mongodb.ClientEncryptionSettings; import com.mongodb.ConnectionString; @@ -52,16 +52,17 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { @Override public MongoCustomConversions customConversions() { - return new MongoCustomConversions(Arrays.asList(new StringConverter(encryptionConfig), new IntegerConverter(encryptionConfig))); + return new MongoCustomConversions(Arrays.asList(new BinaryConverter())); } + @Bean @Override public MongoClient mongoClient() { MongoClient client; try { client = MongoClients.create(clientSettings()); - ClientEncryption encryption = createClientEncryption(); + ClientEncryption encryption = clientEncryption(); encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption)); return client; @@ -70,6 +71,19 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { } } + @Bean + public ClientEncryption clientEncryption() throws FileNotFoundException, IOException { + Map> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath()); + + ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder() + .keyVaultMongoClientSettings(clientSettings()) + .keyVaultNamespace(encryptionConfig.getKeyVaultNamespace()) + .kmsProviders(kmsProviders) + .build(); + + return ClientEncryptions.create(encryptionSettings); + } + private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) { MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace()); MongoCollection keyVault = client.getDatabase(namespace.getDatabaseName()) @@ -92,19 +106,6 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { } } - private ClientEncryption createClientEncryption() throws FileNotFoundException, IOException { - Map> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath()); - - ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(clientSettings()) - .keyVaultNamespace(encryptionConfig.getKeyVaultNamespace()) - .kmsProviders(kmsProviders) - .build(); - - encryptionConfig.setEncryption(ClientEncryptions.create(encryptionSettings)); - return encryptionConfig.getEncryption(); - } - private MongoClientSettings clientSettings() throws FileNotFoundException, IOException { Builder settings = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(uri)); diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/BinaryConverter.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/BinaryConverter.java new file mode 100644 index 0000000000..15231551fc --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/BinaryConverter.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.csfle.config.converter; + +import org.bson.BsonBinary; +import org.bson.types.Binary; +import org.springframework.core.convert.converter.Converter; + +public class BinaryConverter implements Converter { + + @Override + public BsonBinary convert(Binary source) { + return new BsonBinary(source.getType(), source.getData()); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java deleted file mode 100644 index 020513ebff..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.baeldung.boot.csfle.config.converter; - -import org.bson.BsonBinary; -import org.bson.BsonValue; -import org.bson.types.Binary; -import org.springframework.core.convert.converter.Converter; - -import com.baeldung.boot.csfle.config.EncryptionConfig; - -public class IntegerConverter implements Converter { - - private EncryptionConfig encryptionConfig; - - public IntegerConverter(EncryptionConfig config) { - this.encryptionConfig = config; - } - - @Override - public Integer convert(Binary source) { - BsonBinary bin = new BsonBinary(source.getType(), source.getData()); - BsonValue value = encryptionConfig.getEncryption() - .decrypt(bin); - - return value.asInt32() - .getValue(); - } -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java deleted file mode 100644 index 7f8193ce43..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.baeldung.boot.csfle.config.converter; - -import org.bson.BsonBinary; -import org.bson.BsonValue; -import org.bson.types.Binary; -import org.springframework.core.convert.converter.Converter; - -import com.baeldung.boot.csfle.config.EncryptionConfig; - -public class StringConverter implements Converter { - - private EncryptionConfig encryptionConfig; - - public StringConverter(EncryptionConfig config) { - this.encryptionConfig = config; - } - - @Override - public String convert(Binary source) { - BsonBinary bin = new BsonBinary(source.getType(), source.getData()); - BsonValue value = encryptionConfig.getEncryption() - .decrypt(bin); - - return value.asString() - .getValue(); - } -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java index 9cc0753289..91b5940b25 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java @@ -1,6 +1,7 @@ package com.baeldung.boot.csfle.service; import java.util.List; +import java.util.stream.Collectors; import org.bson.BsonBinary; import org.bson.BsonInt32; @@ -16,6 +17,7 @@ import com.baeldung.boot.csfle.config.EncryptionConfig; import com.baeldung.boot.csfle.data.Citizen; import com.baeldung.boot.csfle.data.EncryptedCitizen; import com.mongodb.client.model.vault.EncryptOptions; +import com.mongodb.client.vault.ClientEncryption; @Service public class CitizenService { @@ -29,6 +31,9 @@ public class CitizenService { @Autowired private EncryptionConfig encryptionConfig; + @Autowired + private ClientEncryption clientEncryption; + public EncryptedCitizen save(Citizen citizen) { EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen); encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM)); @@ -38,26 +43,68 @@ public class CitizenService { } public List findAll() { - return mongo.findAll(Citizen.class); + if (!encryptionConfig.getAutoDecryption()) { + List allEncrypted = mongo.findAll(EncryptedCitizen.class); + + return allEncrypted.stream() + .map(this::decrypt) + .collect(Collectors.toList()); + } else { + return mongo.findAll(Citizen.class); + } } public Citizen findByEmail(String email) { Query byEmail = new Query(Criteria.where("email") .is(encrypt(email, DETERMINISTIC_ALGORITHM))); - return mongo.findOne(byEmail, Citizen.class); + if (!encryptionConfig.getAutoDecryption()) { + EncryptedCitizen encryptedCitizen = mongo.findOne(byEmail, EncryptedCitizen.class); + return decrypt(encryptedCitizen); + } else { + return mongo.findOne(byEmail, Citizen.class); + } } public BsonBinary encrypt(Object value, String algorithm) { if (value == null) return null; - BsonValue bsonValue = value instanceof Integer - ? new BsonInt32((Integer) value) - : new BsonString(value.toString()); + BsonValue bsonValue; + if (value instanceof Integer) { + bsonValue = new BsonInt32((Integer) value); + } else if (value instanceof String) { + bsonValue = new BsonString((String) value); + } else { + throw new IllegalArgumentException("unsupported type: " + value.getClass()); + } EncryptOptions options = new EncryptOptions(algorithm); options.keyId(encryptionConfig.getDataKeyId()); - return encryptionConfig.getEncryption() - .encrypt(bsonValue, options); + return clientEncryption.encrypt(bsonValue, options); + } + + public BsonValue decryptProperty(BsonBinary value) { + if (value == null) + return null; + + return clientEncryption.decrypt(value); + } + + private Citizen decrypt(EncryptedCitizen encrypted) { + Citizen citizen = new Citizen(encrypted); + + BsonValue decryptedBirthYear = decryptProperty(encrypted.getBirthYear()); + if (decryptedBirthYear != null) { + citizen.setBirthYear(decryptedBirthYear.asInt32() + .intValue()); + } + + BsonValue decryptedEmail = decryptProperty(encrypted.getEmail()); + if (decryptedEmail != null) { + citizen.setEmail(decryptedEmail.asString() + .getValue()); + } + + return citizen; } } diff --git a/persistence-modules/spring-data-jpa-query-3/pom.xml b/persistence-modules/spring-data-jpa-query-3/pom.xml index 135d31aaba..18df57fe14 100644 --- a/persistence-modules/spring-data-jpa-query-3/pom.xml +++ b/persistence-modules/spring-data-jpa-query-3/pom.xml @@ -5,6 +5,9 @@ 4.0.0 spring-data-jpa-query-3 spring-data-jpa-query-3 + + 0.15 + com.baeldung @@ -22,6 +25,11 @@ com.h2database h2 + + com.github.javafaker + javafaker + ${javafaker.version} + org.springframework.boot spring-boot-starter-test diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/ListVsStreamQueryApplication.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/ListVsStreamQueryApplication.java new file mode 100644 index 0000000000..58123afa6c --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/ListVsStreamQueryApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.spring.data.jpa.collectionsvsstream; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ListVsStreamQueryApplication { + + public static void main(String[] args) { + SpringApplication.run(ListVsStreamQueryApplication.class, args); + } +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/User.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/User.java new file mode 100644 index 0000000000..d2174c343f --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/User.java @@ -0,0 +1,61 @@ +package com.baeldung.spring.data.jpa.collectionsvsstream; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "_user") +public class User { + private String firstName; + private String lastName; + private int age; + @Id + private int id; + + public User() { + } + + public User(String firstName, String lastName, int age) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } + + public User(String firstName, String lastName, int age, int id) { + this(firstName, lastName, age); + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepository.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepository.java new file mode 100644 index 0000000000..ed37cb7036 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepository.java @@ -0,0 +1,14 @@ +package com.baeldung.spring.data.jpa.collectionsvsstream; + +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + Stream findAllByAgeGreaterThan(int age); + + List findByAgeGreaterThan(int age); +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepositoryUnitTest.java b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepositoryUnitTest.java new file mode 100644 index 0000000000..3a0342bf41 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/collectionsvsstream/UserRepositoryUnitTest.java @@ -0,0 +1,58 @@ +package com.baeldung.spring.data.jpa.collectionsvsstream; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterEach; +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 com.github.javafaker.Faker; + +@DataJpaTest +class UserRepositoryUnitTest { + + @Autowired + private UserRepository userRepository; + + @BeforeEach + public void setup() { + Faker faker = new Faker(); + List people = IntStream.range(1, 100) + .parallel() + .mapToObj(i -> new User(faker.name() + .firstName(), faker.name() + .lastName(), faker.number() + .numberBetween(1, 100), i)) + .collect(Collectors.toList()); + userRepository.saveAll(people); + } + + @AfterEach + public void tearDown() { + userRepository.deleteAll(); + } + + @Test + public void whenAgeIs20_thenItShouldReturnAllUsersWhoseAgeIsGreaterThan20InAList() { + List users = userRepository.findByAgeGreaterThan(20); + assertThat(users).isNotEmpty(); + assertThat(users.stream() + .map(User::getAge) + .allMatch(age -> age > 20)).isTrue(); + } + + @Test + public void whenAgeIs20_thenItShouldReturnAllUsersWhoseAgeIsGreaterThan20InAStream() { + Stream users = userRepository.findAllByAgeGreaterThan(20); + assertThat(users).isNotNull(); + assertThat(users.map(User::getAge) + .allMatch(age -> age > 20)).isTrue(); + } +} diff --git a/pom.xml b/pom.xml index ea69fc7570..14f3c4e5ba 100644 --- a/pom.xml +++ b/pom.xml @@ -332,12 +332,6 @@ apache-cxf-modules apache-libraries - apache-poi - apache-velocity - di-modules - asciidoctor - - aws-modules azure checker-plugin @@ -614,12 +608,6 @@ apache-cxf-modules apache-libraries - apache-poi - apache-velocity - di-modules - asciidoctor - - aws-modules azure checker-plugin @@ -907,6 +895,11 @@ algorithms-modules + apache-poi + apache-velocity + di-modules + asciidoctor + aws-modules core-java-modules/core-java-9 core-java-modules/core-java-9-improvements core-java-modules/core-java-9-jigsaw @@ -963,6 +956,7 @@ spring-boot-modules/spring-boot-3 spring-boot-modules/spring-boot-3-native spring-boot-modules/spring-boot-3-observation + spring-boot-modules/spring-boot-3-test-pitfalls spring-swagger-codegen/custom-validations-opeanpi-codegen testing-modules/testing-assertions persistence-modules/fauna @@ -1106,6 +1100,12 @@ algorithms-modules + apache-poi + apache-velocity + di-modules + asciidoctor + aws-modules + core-java-modules/core-java-9 core-java-modules/core-java-9-improvements core-java-modules/core-java-9-jigsaw @@ -1162,6 +1162,7 @@ spring-boot-modules/spring-boot-3 spring-boot-modules/spring-boot-3-native spring-boot-modules/spring-boot-3-observation + spring-boot-modules/spring-boot-3-test-pitfalls spring-swagger-codegen/custom-validations-opeanpi-codegen testing-modules/testing-assertions persistence-modules/fauna @@ -1283,7 +1284,7 @@ 11 - + parents diff --git a/quarkus-modules/quarkus-funqy/pom.xml b/quarkus-modules/quarkus-funqy/pom.xml index 95d712b9fa..603f458287 100644 --- a/quarkus-modules/quarkus-funqy/pom.xml +++ b/quarkus-modules/quarkus-funqy/pom.xml @@ -16,6 +16,11 @@ 2.16.0.Final 3.0.0-M7 + + com.baeldung + quarkus-modules + 1.0.0-SNAPSHOT + diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/pom.xml b/spring-boot-modules/spring-boot-3-test-pitfalls/pom.xml new file mode 100644 index 0000000000..90e4ba022a --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + spring-boot-3-test-pitfalls + 0.0.1-SNAPSHOT + spring-boot-3-test-pitfalls + Demo project for Spring Boot Testing Pitfalls + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + + + + 1.5.3.Final + 3.0.0-M7 + + + + diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/PetsApplication.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/PetsApplication.java new file mode 100644 index 0000000000..17ba1abb2e --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/PetsApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.sample.pets; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PetsApplication { + + public static void main(String[] args) { + SpringApplication.run(PetsApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDto.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDto.java new file mode 100644 index 0000000000..62b1202ce8 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDto.java @@ -0,0 +1,10 @@ +package com.baeldung.sample.pets.boundary; + +import lombok.Data; + +@Data +public class PetDto { + + private String name; + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDtoMapper.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDtoMapper.java new file mode 100644 index 0000000000..07fbb77f2e --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetDtoMapper.java @@ -0,0 +1,13 @@ +package com.baeldung.sample.pets.boundary; + +import com.baeldung.sample.pets.domain.Pet; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface PetDtoMapper { + + PetDto map(Pet source); + + Pet map(PetDto source); + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetsController.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetsController.java new file mode 100644 index 0000000000..d95d43028b --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/boundary/PetsController.java @@ -0,0 +1,28 @@ +package com.baeldung.sample.pets.boundary; + +import com.baeldung.sample.pets.domain.PetService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/pets") +@RequiredArgsConstructor +public class PetsController { + + private final PetService service; + private final PetDtoMapper mapper; + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public Collection readAll() { + return service.getPets().stream() + .map(mapper::map) + .collect(Collectors.toList()); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/Pet.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/Pet.java new file mode 100644 index 0000000000..3dfe5e1a47 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/Pet.java @@ -0,0 +1,4 @@ +package com.baeldung.sample.pets.domain; + +public record Pet(String name) { +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetService.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetService.java new file mode 100644 index 0000000000..c5f83047b8 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetService.java @@ -0,0 +1,14 @@ +package com.baeldung.sample.pets.domain; + +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PetService { + + @Delegate + private final PetServiceRepository repo; + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepository.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepository.java new file mode 100644 index 0000000000..1900f72a1c --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepository.java @@ -0,0 +1,13 @@ +package com.baeldung.sample.pets.domain; + +import java.util.Collection; + +public interface PetServiceRepository { + + boolean add(Pet pet); + + void clear(); + + Collection getPets(); + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepositoryImpl.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepositoryImpl.java new file mode 100644 index 0000000000..d1d4bba175 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/java/com/baeldung/sample/pets/domain/PetServiceRepositoryImpl.java @@ -0,0 +1,29 @@ +package com.baeldung.sample.pets.domain; + +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Component +public class PetServiceRepositoryImpl implements PetServiceRepository { + + private final Set pets = new HashSet<>(); + + @Override + public Set getPets() { + return Collections.unmodifiableSet(pets); + } + + @Override + public boolean add(Pet pet) { + return this.pets.add(pet); + } + + @Override + public void clear() { + this.pets.clear(); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/resources/application.yml new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/main/resources/application.yml @@ -0,0 +1 @@ + diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetDtoMapperIntegrationTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetDtoMapperIntegrationTest.java new file mode 100644 index 0000000000..14b12cb6e9 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetDtoMapperIntegrationTest.java @@ -0,0 +1,47 @@ +package com.baeldung.sample.pets.boundary; + +import com.baeldung.sample.pets.domain.PetService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@ExtendWith(SpringExtension.class) +public class PetDtoMapperIntegrationTest { + + @Configuration + @ComponentScan(basePackageClasses = PetDtoMapper.class) + static class PetDtoMapperTestConfig { + + /* + * This would be necessary because the controller is also initialized + * and needs the service, although we do not want to test it here. + * + * Solutions: + * - place the mapper into a separate sub package + * - do not test the mapper separately, test it integrated within the controller + * (recommended) + */ + @Bean + PetService createServiceMock() { + return mock(PetService.class, MockReset.withSettings(MockReset.AFTER)); + } + + } + + @Autowired + PetDtoMapper mapper; + + @Test + void shouldExist() { // simply test correct test setup + assertThat(mapper).isNotNull(); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsBoundaryLayer.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsBoundaryLayer.java new file mode 100644 index 0000000000..2a83b364c3 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsBoundaryLayer.java @@ -0,0 +1,10 @@ +package com.baeldung.sample.pets.boundary; + +import org.springframework.context.annotation.ComponentScan; + +/** + * Just an interface to use for compiler-checked component scanning during tests. + * @see ComponentScan#basePackageClasses() + */ +public interface PetsBoundaryLayer { +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsControllerMvcIntegrationTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsControllerMvcIntegrationTest.java new file mode 100644 index 0000000000..9a5df7b727 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/boundary/PetsControllerMvcIntegrationTest.java @@ -0,0 +1,36 @@ +package com.baeldung.sample.pets.boundary; + +import com.baeldung.sample.pets.domain.PetService; +import com.baeldung.sample.test.slices.PetsBoundaryTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Collections; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@PetsBoundaryTest +class PetsControllerMvcIntegrationTest { + + @Autowired + MockMvc mvc; + @Autowired + PetService service; + + @Test + void shouldReturnEmptyArrayWhenGetPets() throws Exception { + when(service.getPets()).thenReturn(Collections.emptyList()); + mvc.perform( + get("/pets") + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(content().string("[]")); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceIntegrationTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceIntegrationTest.java new file mode 100644 index 0000000000..5e2ec41089 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceIntegrationTest.java @@ -0,0 +1,26 @@ +package com.baeldung.sample.pets.domain; + +import com.baeldung.sample.test.slices.PetsDomainTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@PetsDomainTest +class PetServiceIntegrationTest { + + @Autowired + PetService service; + @Autowired // Mock + PetServiceRepository repository; + + @Test + void shouldAddPetWhenNotAlreadyExisting() { + var pet = new Pet("Dog"); + when(repository.add(pet)).thenReturn(true); + var result = service.add(pet); + assertThat(result).isTrue(); + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceUnitTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceUnitTest.java new file mode 100644 index 0000000000..b0ecdfaeb7 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetServiceUnitTest.java @@ -0,0 +1,31 @@ +package com.baeldung.sample.pets.domain; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PetServiceUnitTest { + + PetService service = new PetService(new PetServiceRepositoryImpl()); + + @Test + void shouldAddPetWhenNotAlreadyExisting() { + var pet = new Pet("Dog"); + var result = service.add(pet); + assertThat(result).isTrue(); + assertThat(service.getPets()).hasSize(1); + } + + @Test + void shouldNotAddPetWhenAlreadyExisting() { + var pet = new Pet("Cat"); + var result = service.add(pet); + assertThat(result).isTrue(); + // try a second time + result = service.add(pet); + assertThat(result).isFalse(); + assertThat(service.getPets()).hasSize(1); + } + + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetsDomainLayer.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetsDomainLayer.java new file mode 100644 index 0000000000..f32fd189e0 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/pets/domain/PetsDomainLayer.java @@ -0,0 +1,10 @@ +package com.baeldung.sample.pets.domain; + +import org.springframework.context.annotation.ComponentScan; + +/** + * Just an interface to use for compiler-checked component scanning during tests. + * @see ComponentScan#basePackageClasses() + */ +public interface PetsDomainLayer { +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsBoundaryTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsBoundaryTest.java new file mode 100644 index 0000000000..f58239f971 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsBoundaryTest.java @@ -0,0 +1,52 @@ +package com.baeldung.sample.test.slices; + +import com.baeldung.sample.pets.boundary.PetsBoundaryLayer; +import com.baeldung.sample.pets.boundary.PetsController; +import com.baeldung.sample.pets.domain.PetService; +import org.junit.jupiter.api.Tag; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.ActiveProfiles; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static org.mockito.Mockito.mock; + +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@WebMvcTest(controllers = PetsController.class) +@ComponentScan(basePackageClasses = PetsBoundaryLayer.class) +@Import(PetsBoundaryTest.PetBoundaryTestConfiguration.class) +// further features that can help to configure and execute tests +@ActiveProfiles({ "test", "boundary-test" }) +@Tag("integration-test") +@Tag("boundary-test") +public @interface PetsBoundaryTest { + + @TestConfiguration + class PetBoundaryTestConfiguration { + + @Primary + @Bean + PetService createPetServiceMock() { + return mock( + PetService.class, + MockReset.withSettings(MockReset.AFTER) + ); + } + + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsDomainTest.java b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsDomainTest.java new file mode 100644 index 0000000000..b061889135 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/java/com/baeldung/sample/test/slices/PetsDomainTest.java @@ -0,0 +1,52 @@ +package com.baeldung.sample.test.slices; + +import com.baeldung.sample.pets.domain.PetServiceRepository; +import com.baeldung.sample.pets.domain.PetsDomainLayer; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static org.mockito.Mockito.mock; + +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ExtendWith(SpringExtension.class) +@ComponentScan(basePackageClasses = PetsDomainLayer.class) +@Import(PetsDomainTest.PetServiceTestConfiguration.class) +// further features that can help to configure and execute tests +@ActiveProfiles({"test", "domain-test"}) +@Tag("integration-test") +@Tag("domain-test") +public @interface PetsDomainTest { + + @TestConfiguration + class PetServiceTestConfiguration { + + @Primary + @Bean + PetServiceRepository createPetsRepositoryMock() { + return mock( + PetServiceRepository.class, + MockReset.withSettings(MockReset.AFTER) + ); + } + + } + +} diff --git a/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/resources/application-test.yml b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/resources/application-test.yml new file mode 100644 index 0000000000..9801fe9e7e --- /dev/null +++ b/spring-boot-modules/spring-boot-3-test-pitfalls/src/test/resources/application-test.yml @@ -0,0 +1,11 @@ +logging: + level: + root: info + org: + springframework: + test: + context: + cache: DEBUG +spring: + main: + allow-bean-definition-overriding: true diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/SingletonBeanConfig.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/SingletonBeanConfig.java new file mode 100644 index 0000000000..1c40097ba5 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/SingletonBeanConfig.java @@ -0,0 +1,29 @@ +package com.baeldung.sample.singleton; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +public class SingletonBeanConfig { + + @Bean + @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) + public SingletonBean singletonBean() { + return new SingletonBean(); + } + + @Bean + @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) + public SingletonBean anotherSingletonBean() { + return new SingletonBean(); + } + + static class SingletonBean { + public String getValue() { + return "test"; + } + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/ThreadSafeSingleInstance.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/ThreadSafeSingleInstance.java new file mode 100644 index 0000000000..ae6e85bf2b --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/singleton/ThreadSafeSingleInstance.java @@ -0,0 +1,24 @@ +package com.baeldung.sample.singleton; + +public final class ThreadSafeSingleInstance { + + private static volatile ThreadSafeSingleInstance instance = null; + + private ThreadSafeSingleInstance() {} + + public static ThreadSafeSingleInstance getInstance() { + if (instance == null) { + synchronized(ThreadSafeSingleInstance.class) { + if (instance == null) { + instance = new ThreadSafeSingleInstance(); + } + } + } + return instance; + } + + public String getValue() { + return "test"; + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/SingletonBeanUnitTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/SingletonBeanUnitTest.java new file mode 100644 index 0000000000..72c1ae4170 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/SingletonBeanUnitTest.java @@ -0,0 +1,35 @@ +package com.baeldung.sample.singleton; + +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SingletonBeanUnitTest { + + @Autowired + @Qualifier("singletonBean") + private SingletonBeanConfig.SingletonBean beanOne; + + @Autowired + @Qualifier("singletonBean") + private SingletonBeanConfig.SingletonBean beanTwo; + + @Autowired + @Qualifier("anotherSingletonBean") + private SingletonBeanConfig.SingletonBean beanThree; + + @Test + void givenTwoBeansWithSameId_whenInjectingThem_thenSameInstancesAreReturned() { + assertSame(beanOne, beanTwo); + } + + @Test + void givenTwoBeansWithDifferentId_whenInjectingThem_thenDifferentInstancesAreReturned() { + assertNotSame(beanOne, beanThree); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/ThreadSafeSingleInstanceUnitTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/ThreadSafeSingleInstanceUnitTest.java new file mode 100644 index 0000000000..0753f92e31 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/singleton/ThreadSafeSingleInstanceUnitTest.java @@ -0,0 +1,16 @@ +package com.baeldung.sample.singleton; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertSame; + +class ThreadSafeSingleInstanceUnitTest { + + @Test + void givenTwoSingletonInstances_whenGettingThem_thenSameInstancesAreReturned() { + ThreadSafeSingleInstance instanceOne = ThreadSafeSingleInstance.getInstance(); + ThreadSafeSingleInstance instanceTwo = ThreadSafeSingleInstance.getInstance(); + assertSame(instanceOne, instanceTwo); + } + +} diff --git a/spring-reactive-modules/spring-reactive/README.md b/spring-reactive-modules/spring-reactive/README.md index 9f1852d912..7dfc7b2952 100644 --- a/spring-reactive-modules/spring-reactive/README.md +++ b/spring-reactive-modules/spring-reactive/README.md @@ -1,3 +1,7 @@ +### Spring Reactive Articles that are also part of the e-book + +This module contains articles about Spring Reactive that are also part of an Ebook. + ## Spring Reactive This module contains articles describing reactive processing in Spring. @@ -13,4 +17,8 @@ This module contains articles describing reactive processing in Spring. - [Spring WebClient Requests with Parameters](https://www.baeldung.com/webflux-webclient-parameters) - [Handling Errors in Spring WebFlux](https://www.baeldung.com/spring-webflux-errors) - [Spring Security 5 for Reactive Applications](https://www.baeldung.com/spring-security-5-reactive) -- [Concurrency in Spring WebFlux](https://www.baeldung.com/spring-webflux-concurrency) \ No newline at end of file +- [Concurrency in Spring WebFlux](https://www.baeldung.com/spring-webflux-concurrency) + +### NOTE: + +Since this is a module tied to an e-book, it should **not** be moved or used to store the code for any further article. diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Article.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Article.java new file mode 100644 index 0000000000..6beac24827 --- /dev/null +++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Article.java @@ -0,0 +1,19 @@ +package com.baeldung.junit5.nested; + +public class Article { + private String name; + private Membership articleLevel; + + public Article(String name, Membership articleLevel) { + this.name = name; + this.articleLevel = articleLevel; + } + + public String getName() { + return name; + } + + public Membership getArticleLevel() { + return articleLevel; + } +} \ No newline at end of file diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Membership.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Membership.java new file mode 100644 index 0000000000..de8e6f1fc8 --- /dev/null +++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Membership.java @@ -0,0 +1,16 @@ +package com.baeldung.junit5.nested; + +public enum Membership { + FREE(0), SILVER(10), GOLD(20); + + private final int level; + + Membership(int level) { + this.level = level; + } + + public int compare(Membership other) { + return level - other.level; + } + +} \ No newline at end of file diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Publication.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Publication.java new file mode 100644 index 0000000000..bfadbd3fe8 --- /dev/null +++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/Publication.java @@ -0,0 +1,32 @@ +package com.baeldung.junit5.nested; + +import java.util.List; +import java.util.stream.Collectors; + +public class Publication { + private final List
articles; + + public Publication(List
articles) { + this.articles = articles; + } + + public List getReadableArticles(User user) { + return articles.stream() + .filter(a -> a.getArticleLevel() + .compare(user.getMembership()) <= 0) + .map(Article::getName) + .collect(Collectors.toList()); + } + + public List getLockedArticles(User user) { + return articles.stream() + .filter(a -> a.getArticleLevel() + .compare(user.getMembership()) > 0) + .map(Article::getName) + .collect(Collectors.toList()); + } + + public List
getArticles() { + return articles; + } +} \ No newline at end of file diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/User.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/User.java new file mode 100644 index 0000000000..3e9d7ebeff --- /dev/null +++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/nested/User.java @@ -0,0 +1,19 @@ +package com.baeldung.junit5.nested; + +public class User { + private String name; + private Membership membership; + + public User(String name, Membership membership) { + this.name = name; + this.membership = membership; + } + + public String getName() { + return name; + } + + public Membership getMembership() { + return membership; + } +} \ No newline at end of file diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/NestedUnitTest.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/NestedUnitTest.java new file mode 100644 index 0000000000..e30094cc9d --- /dev/null +++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/NestedUnitTest.java @@ -0,0 +1,41 @@ +package com.baeldung.junit5.nested; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class NestedUnitTest { + + @BeforeEach + void beforeEach() { + System.out.println("NestedUnitTest.beforeEach()"); + } + + @Nested + class FirstNestedClass { + @BeforeEach + void beforeEach() { + System.out.println("FirstNestedClass.beforeEach()"); + } + + @Test + void test() { + System.out.println("FirstNestedClass.test()"); + } + } + + + @Nested + class SecondNestedClass { + @BeforeEach + void beforeEach() { + System.out.println("SecondNestedClass.beforeEach()"); + } + + @Test + void test() { + System.out.println("SecondNestedClass.test()"); + } + } + +} diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/OnlinePublicationUnitTest.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/OnlinePublicationUnitTest.java new file mode 100644 index 0000000000..bf3f15ff4e --- /dev/null +++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/nested/OnlinePublicationUnitTest.java @@ -0,0 +1,93 @@ +package com.baeldung.junit5.nested; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("given a article publication with three articles") +class OnlinePublicationUnitTest { + private Publication publication; + + @BeforeEach + void setupArticlesAndPublication() { + Article freeArticle = new Article("free article", Membership.FREE); + Article silverArticle = new Article("silver level article", Membership.SILVER); + Article goldArticle = new Article("gold level article", Membership.GOLD); + publication = new Publication(Arrays.asList(freeArticle, silverArticle, goldArticle)); + } + + @Test + @DisplayName("then 3 articles are available") + void shouldHaveThreeArticlesInTotal() { + List
allArticles = publication.getArticles(); + assertThat(allArticles).hasSize(3); + } + + @Nested + @DisplayName("when a user with a 'free' membership logs in") + class UserWithAFreeMembership { + User freeFreya = new User("Freya", Membership.FREE); + + @Test + @DisplayName("then he should be able to read the 'free' articles") + void shouldOnlyReadFreeArticles() { + List articles = publication.getReadableArticles(freeFreya); + assertThat(articles).containsExactly("free article"); + } + + @Test + @DisplayName("then he shouldn't be able to read the 'silver' and 'gold' articles") + void shouldSeeSilverAndGoldLevelArticlesAsLocked() { + List articles = publication.getLockedArticles(freeFreya); + assertThat(articles).containsExactlyInAnyOrder("silver level article", "gold level article"); + } + } + + @Nested + @DisplayName("when a user with a 'silver' membership logs in") + class UserWithSilverMembership { + User silverSilvester = new User("Silvester", Membership.SILVER); + + @Test + @DisplayName("then he should be able to read the 'free' and 'silver' level articles") + void shouldOnlyReadFreeAndSilverLevelArticles() { + List articles = publication.getReadableArticles(silverSilvester); + assertThat(articles).containsExactlyInAnyOrder("free article", "silver level article"); + } + + @Test + @DisplayName("then he should see the 'gold' level articles as locked") + void shouldSeeGoldLevelArticlesAsLocked() { + List articles = publication.getLockedArticles(silverSilvester); + assertThat(articles).containsExactlyInAnyOrder("gold level article"); + } + } + + @Nested + @DisplayName("when a user with a 'gold' membership logs in") + class UserWithGoldMembership { + User goldenGeorge = new User("George", Membership.GOLD); + + @Test + @DisplayName("then he should be able to read all the articles") + void shouldSeeAllArticles() { + List articles = publication.getReadableArticles(goldenGeorge); + assertThat(articles).containsExactlyInAnyOrder("free article", "silver level article", "gold level article"); + } + + @Test + @DisplayName("then he should not see any article as locked") + void shouldNotHaveHiddenArticles() { + List articles = publication.getLockedArticles(goldenGeorge); + assertThat(articles).isEmpty(); + } + + } + +} \ No newline at end of file