├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spring boot test.postman_collection.json └── src ├── main ├── java │ └── com │ │ └── gucardev │ │ └── springboottest │ │ ├── SpringBootTestApplication.java │ │ ├── config │ │ ├── CacheCustomizer.java │ │ ├── CachingConfigurationProperties.java │ │ └── InitialDataCreator.java │ │ ├── constant │ │ └── Constants.java │ │ ├── controller │ │ ├── AddressController.java │ │ └── UserController.java │ │ ├── dto │ │ ├── AddressDTO.java │ │ ├── RestPageResponse.java │ │ ├── UserDTO.java │ │ ├── converter │ │ │ ├── AddressConverter.java │ │ │ └── UserConverter.java │ │ └── request │ │ │ ├── AddressRequest.java │ │ │ └── UserRequest.java │ │ ├── exception │ │ ├── GlobalExceptionHandler.java │ │ └── NotFoundException.java │ │ ├── model │ │ ├── Address.java │ │ ├── BaseEntity.java │ │ ├── User.java │ │ └── projection │ │ │ ├── MailUserNameProjection.java │ │ │ └── UsernameLengthProjection.java │ │ ├── remote │ │ └── RemoteUserClient.java │ │ ├── repository │ │ ├── AddressRepository.java │ │ ├── UserCustomRepository.java │ │ ├── UserCustomRepositoryImpl.java │ │ └── UserRepository.java │ │ ├── service │ │ ├── AddressService.java │ │ ├── UserService.java │ │ └── impl │ │ │ ├── AddressServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ └── spesification │ │ └── UserSpecifications.java └── resources │ ├── application-test.yml │ └── application.yml └── test ├── java └── com │ └── gucardev │ └── springboottest │ ├── SpringBootTestApplicationTests.java │ ├── config │ └── CachingConfigurationPropertiesTest.java │ ├── constant │ └── ConstantsTest.java │ ├── controller │ ├── AddressControllerIntegrationTest.java │ ├── AddressControllerIntegrationTestSupport.java │ ├── UserControllerIntegrationTestSupport.java │ ├── UserControllerUnitTest.java │ └── UserControllerUserControllerIntegrationTest.java │ ├── dto │ ├── converter │ │ └── UserConverterTest.java │ └── request │ │ └── UserRequestTest.java │ ├── exception │ ├── GlobalExceptionHandlerTest.java │ └── NotFoundExceptionTest.java │ ├── model │ ├── AddressTest.java │ └── UserTest.java │ ├── repository │ ├── AddressRepositoryTest.java │ └── UserRepositoryTest.java │ └── service │ └── impl │ ├── AddressServiceTest.java │ ├── AddressServiceTestSupport.java │ ├── UserServiceTest.java │ └── UserServiceTestSupport.java └── resources └── db └── addressData.sql /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/spring-boot-test/fb88cbcab1b332aabd59e2c502b35543c8cda939/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## spring boot unit & integration tests of components 2 | - service layer 3 | - repository 4 | - controller unit & integration 5 | - dto converter 6 | - entity getter/setter 7 | - filter/pagination/sorting specification tests 8 | - rate limiter 9 | - scheduler 10 | - wiremock 11 | 12 | ### how to run all tests 13 | 14 | ```shell 15 | mvn test 16 | ``` 17 | 18 | ### test method name format 19 | 20 | #### format: 21 | 22 | - **method_given_expected** 23 | 24 | #### examples: 25 | 26 | - create_givenNewUser_returnCreatedUser 27 | - getById_givenNonExistentId_throwException 28 | - findAllMailAndUserName_givenNoCondition_returnMailAndUsernames 29 | 30 | ### Example service unit tests 31 | 32 | ```java 33 | 34 | @Test 35 | @DisplayName("findAll returns all users with pagination") 36 | void findAll_givenPageable_returnUsers(){ 37 | Page usersPage=new PageImpl<>(Arrays.asList(user1,user2)); 38 | 39 | when(userRepository.findAll(any(Specification.class),any(Pageable.class))) 40 | .thenReturn(usersPage); 41 | when(userConverter.mapToDTO(user1)).thenReturn(userDto1); 42 | when(userConverter.mapToDTO(user2)).thenReturn(userDto2); 43 | 44 | Pageable pageable=PageRequest.of(0,5); 45 | Page result=userService.findAll("","name",Sort.Direction.ASC,pageable); 46 | 47 | assertEquals(2,result.getTotalElements()); 48 | assertEquals(1,result.getTotalPages()); 49 | } 50 | 51 | @Test 52 | void getById_givenExistingId_returnUser(){ 53 | when(userRepository.findById(existingUser.getId())).thenReturn(Optional.of(existingUser)); 54 | User result=userService.getById(existingUser.getId()); 55 | assertEquals(existingUser,result); 56 | } 57 | 58 | @Test 59 | void getById_givenNonExistentId_throwException(){ 60 | Long nonExistentId=100L; 61 | when(userRepository.findById(nonExistentId)).thenReturn(Optional.empty()); 62 | assertThrows(RuntimeException.class,()->userService.getById(nonExistentId)); 63 | } 64 | 65 | ``` 66 | 67 | ### Example repository unit tests 68 | 69 | ```java 70 | 71 | @DataJpaTest // !important annotation for repository tests 72 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 73 | class UserRepositoryTest { 74 | 75 | @Autowired 76 | private TestEntityManager entityManager; 77 | 78 | @Autowired 79 | private UserRepository userRepository; 80 | 81 | private User user1, user2, user3, user4; 82 | 83 | @BeforeEach 84 | public void setup() { 85 | 86 | user1 = User.builder().username("user1").name("name1").email("email1@test.com").build(); 87 | ... 88 | user4 = User.builder().username("username4").name("name4").email("email4@test.com").build(); 89 | 90 | entityManager.persist(user1); 91 | ... 92 | entityManager.flush(); 93 | 94 | } 95 | 96 | @Test 97 | void searchByKeyword_givenKeyword_returnMatchingUsers() { 98 | Specification spec = UserSpecifications.searchByKeyword("user1"); 99 | Pageable pageable = PageRequest.of(0, 10); 100 | 101 | Page users = userRepository.findAll(spec, pageable); 102 | 103 | assertEquals(1, users.getContent().size()); 104 | assertEquals("user1", users.getContent().get(0).getUsername()); 105 | 106 | } 107 | 108 | @Test 109 | void existsByUsernameIgnoreCase_givenNonExistingUsername_returnFalse() { 110 | boolean exists = userRepository.existsByUsernameIgnoreCase("non-existing-username"); 111 | assertFalse(exists); 112 | } 113 | 114 | @Test 115 | void existsById_givenExistingId_returnTrue() { 116 | boolean exists = userRepository.existsById(user1.getId()); 117 | assertTrue(exists); 118 | } 119 | } 120 | 121 | 122 | ``` 123 | 124 | ### Example controller integration tests 125 | 126 | ```java 127 | @Test 128 | void getById_givenUserId_returnsUser()throws Exception{ 129 | long userId=1L; 130 | MvcResult mvcResult= 131 | mockMvc 132 | .perform( 133 | MockMvcRequestBuilders.get("/api/user/"+userId) 134 | .accept(MediaType.APPLICATION_JSON)) 135 | .andExpect(status().isOk()) 136 | .andReturn(); 137 | 138 | String content=mvcResult.getResponse().getContentAsString(); 139 | UserDTO userDTO=objectMapper.readValue(content,UserDTO.class); 140 | assertEquals(userDTO.getUsername(),"username1"); 141 | } 142 | 143 | ``` 144 | 145 | ### Example controller integration tests using WireMock 146 | 147 | ```java 148 | 149 | WireMockServer wireMockServer=new WireMockServer(3000); 150 | 151 | @BeforeEach 152 | void setupBeforeEach()throws Exception{ 153 | ....... 154 | // wiremock setup 155 | 156 | wireMockServer.start(); 157 | 158 | List mockUserDTOs= 159 | Arrays.asList( 160 | new UserDTO(1L,"User1","user1@example.com","username1"), 161 | new UserDTO(2L,"User2","user2@example.com","username2")); 162 | 163 | RestPageResponse pageResponse=new RestPageResponse<>(mockUserDTOs); 164 | 165 | String jsonResponse=objectMapper.writeValueAsString(pageResponse); 166 | 167 | WireMock.configureFor("localhost",wireMockServer.port()); 168 | stubFor( 169 | get(urlEqualTo("/mock/user")) 170 | .willReturn( 171 | aResponse().withHeader("Content-Type","application/json").withBody(jsonResponse))); 172 | } 173 | 174 | @Test 175 | void differentUsers_returnsMultipleUsers()throws Exception{ 176 | MvcResult mvcResult= 177 | mockMvc 178 | .perform( 179 | MockMvcRequestBuilders.get("/api/user/different-users") 180 | .accept(MediaType.APPLICATION_JSON)) 181 | .andExpect(status().isOk()) 182 | .andReturn(); 183 | 184 | String content=mvcResult.getResponse().getContentAsString(); 185 | List userDTOS=objectMapper.readValue(content,new TypeReference>(){}); 186 | assertTrue(userDTOS.size()>0); 187 | } 188 | 189 | 190 | 191 | 192 | ``` 193 | 194 | ### Example controller unit tests 195 | 196 | ```java 197 | 198 | @Test 199 | void getById_givenUserId_returnsUser(){ 200 | UserDTO userDTO=new UserDTO(); 201 | when(userService.getByIdDTO(anyLong())).thenReturn(userDTO); 202 | ResponseEntity response=userController.getById(1L); 203 | assertEquals(HttpStatus.OK,response.getStatusCode()); 204 | assertEquals(userDTO,response.getBody()); 205 | } 206 | 207 | 208 | ``` 209 | 210 | ### Example validation unit tests 211 | 212 | ```java 213 | @ParameterizedTest 214 | @CsvSource({ 215 | "1, testUsername, test@mail.com, testName, false", 216 | "2, ab, test@mail.com, testName, true", 217 | "3, testUsername, invalidEmail, testName, true", 218 | "4, testUsername, test@mail.com, , true", 219 | " , testUsername, test@mail.com, testName, false", 220 | " ,testUsernameToooooooooooooooooooooooooooooLong, test@mail.com, testName, true", 221 | " , , test@mail.com, testName, true", 222 | " , testUsername, , testName, true", 223 | " , testUsername, test@mail.com, , true" 224 | }) 225 | void testUserRequest( 226 | String idInput,String username,String email,String name,boolean hasViolations){ 227 | Long id=StringUtils.isBlank(idInput)?1L:Long.parseLong(idInput); 228 | 229 | UserRequest userRequest= 230 | UserRequest.builder().id(id).username(username).email(email).name(name).build(); 231 | 232 | Set>violations=validator.validate(userRequest); 233 | assertEquals(hasViolations,!violations.isEmpty()); 234 | } 235 | ``` 236 | 237 | ### Example dto converter unit tests 238 | 239 | ```java 240 | @Test 241 | void mapToEntityTest(){ 242 | UserRequest userRequest=new UserRequest(); 243 | userRequest.setUsername("username"); 244 | userRequest.setEmail("email@test.com"); 245 | userRequest.setName("Test User"); 246 | 247 | User user=userConverter.mapToEntity(userRequest); 248 | 249 | assertEquals(userRequest.getUsername(),user.getUsername()); 250 | assertEquals(userRequest.getEmail(),user.getEmail()); 251 | assertEquals(userRequest.getName(),user.getName()); 252 | } 253 | 254 | ``` 255 | 256 | ## resources: 257 | 258 | [https://github.com/folksdev/movie-api](https://github.com/folksdev/movie-api) 259 | 260 | [https://github.com/folksdev/open-weather](https://github.com/folksdev/open-weather) 261 | 262 | [https://www.bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/](https://www.bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/) 263 | 264 | [https://medium.com/free-code-camp/unit-testing-services-endpoints-and-repositories-in-spring-boot-4b7d9dc2b772](https://medium.com/free-code-camp/unit-testing-services-endpoints-and-repositories-in-spring-boot-4b7d9dc2b772) 265 | 266 | [https://betulsahinn.medium.com/spring-boot-ile-unit-test-yazmak-f1e4fc1f3df](https://betulsahinn.medium.com/spring-boot-ile-unit-test-yazmak-f1e4fc1f3df) 267 | 268 | [https://vladmihalcea.com/spring-jpa-dto-projection/](https://vladmihalcea.com/spring-jpa-dto-projection/) 269 | 270 | [https://medium.com/cuddle-ai/testing-spring-boot-application-using-wiremock-and-junit-5-d514a47ab931](https://medium.com/cuddle-ai/testing-spring-boot-application-using-wiremock-and-junit-5-d514a47ab931) 271 | 272 | [https://www.geeksforgeeks.org/how-to-use-wiremock-with-junit-test/](https://www.geeksforgeeks.org/how-to-use-wiremock-with-junit-test/) 273 | 274 | [https://laurspilca.com/using-wiremock-for-integration-tests-in-spring-apps/](https://laurspilca.com/using-wiremock-for-integration-tests-in-spring-apps/) 275 | 276 | [https://www.baeldung.com/spring-data-testing-cacheable](https://www.baeldung.com/spring-data-testing-cacheable) 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.7.12 10 | 11 | 12 | com.gucardev 13 | spring-boot-test 14 | 0.0.1-SNAPSHOT 15 | spring-boot-test 16 | spring-boot-test 17 | 18 | 1.8 19 | 2021.0.7 20 | dev 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-jpa 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-validation 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-openfeign 46 | 47 | 48 | 49 | 50 | com.h2database 51 | h2 52 | runtime 53 | 54 | 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | true 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-test 66 | test 67 | 68 | 69 | 70 | 71 | org.apache.commons 72 | commons-lang3 73 | 3.12.0 74 | 75 | 76 | 77 | 78 | com.github.tomakehurst 79 | wiremock-standalone 80 | 2.27.2 81 | test 82 | 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-starter-actuator 88 | 2.4.1 89 | 90 | 91 | 92 | 93 | org.springframework.boot 94 | spring-boot-starter-aop 95 | 2.4.1 96 | 97 | 98 | 99 | 100 | io.github.resilience4j 101 | resilience4j-spring-boot2 102 | 1.7.0 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | org.springframework.cloud 114 | spring-cloud-dependencies 115 | ${spring-cloud.version} 116 | pom 117 | import 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-surefire-plugin 131 | 132 | -Dspring.profiles.active=test 133 | 134 | 135 | 136 | 137 | org.springframework.boot 138 | spring-boot-maven-plugin 139 | 140 | 141 | 142 | org.projectlombok 143 | lombok 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /spring boot test.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "66610ceb-49c0-487b-9185-26f2f9c13e29", 4 | "name": "spring boot test", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "23162952" 7 | }, 8 | "item": [ 9 | { 10 | "name": "user", 11 | "item": [ 12 | { 13 | "name": "create 1", 14 | "request": { 15 | "method": "POST", 16 | "header": [], 17 | "body": { 18 | "mode": "raw", 19 | "raw": "{\r\n \"username\": \"grkn\",\r\n \"email\": \"grkn@mail.com\",\r\n \"name\": \"gurkan\"\r\n}", 20 | "options": { 21 | "raw": { 22 | "language": "json" 23 | } 24 | } 25 | }, 26 | "url": { 27 | "raw": "http://localhost:8080/api/user", 28 | "protocol": "http", 29 | "host": [ 30 | "localhost" 31 | ], 32 | "port": "8080", 33 | "path": [ 34 | "api", 35 | "user" 36 | ] 37 | } 38 | }, 39 | "response": [] 40 | }, 41 | { 42 | "name": "create 2", 43 | "request": { 44 | "method": "POST", 45 | "header": [], 46 | "body": { 47 | "mode": "raw", 48 | "raw": "{\r\n \"username\": \"ali\",\r\n \"email\": \"ali@mail.com\",\r\n \"name\": \"ali\"\r\n}", 49 | "options": { 50 | "raw": { 51 | "language": "json" 52 | } 53 | } 54 | }, 55 | "url": { 56 | "raw": "http://localhost:8080/api/user", 57 | "protocol": "http", 58 | "host": [ 59 | "localhost" 60 | ], 61 | "port": "8080", 62 | "path": [ 63 | "api", 64 | "user" 65 | ] 66 | } 67 | }, 68 | "response": [] 69 | }, 70 | { 71 | "name": "create 3", 72 | "request": { 73 | "method": "POST", 74 | "header": [], 75 | "body": { 76 | "mode": "raw", 77 | "raw": "{\r\n \"username\": \"metin\",\r\n \"email\": \"metin@mail.com\",\r\n \"name\": \"metin\"\r\n}", 78 | "options": { 79 | "raw": { 80 | "language": "json" 81 | } 82 | } 83 | }, 84 | "url": { 85 | "raw": "http://localhost:8080/api/user", 86 | "protocol": "http", 87 | "host": [ 88 | "localhost" 89 | ], 90 | "port": "8080", 91 | "path": [ 92 | "api", 93 | "user" 94 | ] 95 | } 96 | }, 97 | "response": [] 98 | }, 99 | { 100 | "name": "create 4", 101 | "request": { 102 | "method": "POST", 103 | "header": [], 104 | "body": { 105 | "mode": "raw", 106 | "raw": "{\r\n \"username\": \"sezai\",\r\n \"email\": \"sezai@mail.com\",\r\n \"name\": \"sezai\"\r\n}", 107 | "options": { 108 | "raw": { 109 | "language": "json" 110 | } 111 | } 112 | }, 113 | "url": { 114 | "raw": "http://localhost:8080/api/user", 115 | "protocol": "http", 116 | "host": [ 117 | "localhost" 118 | ], 119 | "port": "8080", 120 | "path": [ 121 | "api", 122 | "user" 123 | ] 124 | } 125 | }, 126 | "response": [] 127 | }, 128 | { 129 | "name": "get all", 130 | "request": { 131 | "method": "GET", 132 | "header": [], 133 | "url": { 134 | "raw": "http://localhost:8080/api/user", 135 | "protocol": "http", 136 | "host": [ 137 | "localhost" 138 | ], 139 | "port": "8080", 140 | "path": [ 141 | "api", 142 | "user" 143 | ] 144 | } 145 | }, 146 | "response": [] 147 | }, 148 | { 149 | "name": "get different users", 150 | "request": { 151 | "method": "GET", 152 | "header": [], 153 | "url": { 154 | "raw": "http://localhost:8080/api/user/different-users", 155 | "protocol": "http", 156 | "host": [ 157 | "localhost" 158 | ], 159 | "port": "8080", 160 | "path": [ 161 | "api", 162 | "user", 163 | "different-users" 164 | ] 165 | } 166 | }, 167 | "response": [] 168 | }, 169 | { 170 | "name": "get all by username length", 171 | "request": { 172 | "method": "GET", 173 | "header": [], 174 | "url": { 175 | "raw": "http://localhost:8080/api/user/username-length/4", 176 | "protocol": "http", 177 | "host": [ 178 | "localhost" 179 | ], 180 | "port": "8080", 181 | "path": [ 182 | "api", 183 | "user", 184 | "username-length", 185 | "4" 186 | ] 187 | } 188 | }, 189 | "response": [] 190 | }, 191 | { 192 | "name": "get all mail username", 193 | "request": { 194 | "method": "GET", 195 | "header": [], 196 | "url": { 197 | "raw": "http://localhost:8080/api/user/mail-username", 198 | "protocol": "http", 199 | "host": [ 200 | "localhost" 201 | ], 202 | "port": "8080", 203 | "path": [ 204 | "api", 205 | "user", 206 | "mail-username" 207 | ] 208 | } 209 | }, 210 | "response": [] 211 | } 212 | ] 213 | }, 214 | { 215 | "name": "address", 216 | "item": [ 217 | { 218 | "name": "get addresses by user", 219 | "request": { 220 | "method": "GET", 221 | "header": [], 222 | "url": { 223 | "raw": "http://localhost:8080/api/address/user/1", 224 | "protocol": "http", 225 | "host": [ 226 | "localhost" 227 | ], 228 | "port": "8080", 229 | "path": [ 230 | "api", 231 | "address", 232 | "user", 233 | "1" 234 | ] 235 | } 236 | }, 237 | "response": [] 238 | }, 239 | { 240 | "name": "get address by id", 241 | "request": { 242 | "method": "GET", 243 | "header": [], 244 | "url": { 245 | "raw": "http://localhost:8080/api/address/1", 246 | "protocol": "http", 247 | "host": [ 248 | "localhost" 249 | ], 250 | "port": "8080", 251 | "path": [ 252 | "api", 253 | "address", 254 | "1" 255 | ] 256 | } 257 | }, 258 | "response": [] 259 | }, 260 | { 261 | "name": "delete by id", 262 | "request": { 263 | "method": "DELETE", 264 | "header": [], 265 | "url": { 266 | "raw": "http://localhost:8080/api/address/1", 267 | "protocol": "http", 268 | "host": [ 269 | "localhost" 270 | ], 271 | "port": "8080", 272 | "path": [ 273 | "api", 274 | "address", 275 | "1" 276 | ] 277 | } 278 | }, 279 | "response": [] 280 | }, 281 | { 282 | "name": "create address", 283 | "request": { 284 | "method": "POST", 285 | "header": [], 286 | "body": { 287 | "mode": "raw", 288 | "raw": "{\r\n \"title\": \"address title 1\",\r\n \"detail\": \"address detail 1\",\r\n \"userId\": 1\r\n}", 289 | "options": { 290 | "raw": { 291 | "language": "json" 292 | } 293 | } 294 | }, 295 | "url": { 296 | "raw": "http://localhost:8080/api/address", 297 | "protocol": "http", 298 | "host": [ 299 | "localhost" 300 | ], 301 | "port": "8080", 302 | "path": [ 303 | "api", 304 | "address" 305 | ] 306 | } 307 | }, 308 | "response": [] 309 | }, 310 | { 311 | "name": "update address", 312 | "request": { 313 | "method": "PUT", 314 | "header": [], 315 | "body": { 316 | "mode": "raw", 317 | "raw": "{\r\n \"id\": 1,\r\n \"title\": \"address title 1_updated\",\r\n \"detail\": \"address detail 1_updated\",\r\n \"userId\": 1\r\n}", 318 | "options": { 319 | "raw": { 320 | "language": "json" 321 | } 322 | } 323 | }, 324 | "url": { 325 | "raw": "http://localhost:8080/api/address", 326 | "protocol": "http", 327 | "host": [ 328 | "localhost" 329 | ], 330 | "port": "8080", 331 | "path": [ 332 | "api", 333 | "address" 334 | ] 335 | } 336 | }, 337 | "response": [] 338 | } 339 | ] 340 | } 341 | ] 342 | } -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/SpringBootTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest; 2 | 3 | import com.gucardev.springboottest.config.CachingConfigurationProperties; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.cache.annotation.EnableCaching; 8 | import org.springframework.cloud.openfeign.EnableFeignClients; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | 11 | @SpringBootApplication 12 | @EnableFeignClients 13 | @EnableConfigurationProperties(CachingConfigurationProperties.class) 14 | @EnableCaching 15 | @EnableScheduling 16 | public class SpringBootTestApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(SpringBootTestApplication.class, args); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/config/CacheCustomizer.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.config; 2 | 3 | import java.util.Arrays; 4 | import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer; 5 | import org.springframework.cache.concurrent.ConcurrentMapCacheManager; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class CacheCustomizer implements CacheManagerCustomizer { 10 | 11 | private final CachingConfigurationProperties cacheConfig; 12 | 13 | public CacheCustomizer(CachingConfigurationProperties cacheConfig) { 14 | this.cacheConfig = cacheConfig; 15 | } 16 | 17 | @Override 18 | public void customize(ConcurrentMapCacheManager cacheManager) { 19 | cacheManager.setCacheNames( 20 | Arrays.asList( 21 | cacheConfig.getUser().getCacheName(), cacheConfig.getAddress().getCacheName())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/config/CachingConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.config; 2 | 3 | import lombok.Getter; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.ConstructorBinding; 6 | 7 | @ConstructorBinding 8 | @ConfigurationProperties(prefix = "caching.config") 9 | @Getter 10 | public class CachingConfigurationProperties { 11 | 12 | 13 | private final CacheProperties user; 14 | private final CacheProperties address; 15 | 16 | public CachingConfigurationProperties(CacheProperties user, CacheProperties address) { 17 | this.user = user; 18 | this.address = address; 19 | } 20 | 21 | @Getter 22 | public static class CacheProperties { 23 | 24 | private final String cacheName; 25 | private final Long cacheTtl; 26 | 27 | public CacheProperties(String cacheName, Long cacheTtl) { 28 | this.cacheName = cacheName; 29 | this.cacheTtl = cacheTtl; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/config/InitialDataCreator.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.config; 2 | 3 | import com.gucardev.springboottest.model.Address; 4 | import com.gucardev.springboottest.model.User; 5 | import com.gucardev.springboottest.repository.AddressRepository; 6 | import com.gucardev.springboottest.repository.UserRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @Profile("!test") // will work for non "test" profiles 14 | public class InitialDataCreator implements CommandLineRunner { 15 | 16 | // @Autowired 17 | // private Environment env; 18 | 19 | @Autowired UserRepository userRepository; 20 | 21 | @Autowired AddressRepository addressRepository; 22 | 23 | @Override 24 | public void run(String... args) throws Exception { 25 | // second way to check profile 26 | // String profiles[] = env.getActiveProfiles(); 27 | // for (String profile : profiles) { 28 | // if (profile.equals("default")) { 29 | // 30 | // } 31 | // } 32 | 33 | User user1 = 34 | userRepository.save( 35 | User.builder().name("user1").email("mail_user@mail.com").username("user1").build()); 36 | 37 | User user2 = 38 | userRepository.save( 39 | User.builder() 40 | .name("username2") 41 | .email("username2@mail.com") 42 | .username("username2") 43 | .build()); 44 | 45 | User user3 = 46 | userRepository.save( 47 | User.builder() 48 | .name("username3") 49 | .email("username3@mail.com") 50 | .username("username3") 51 | .build()); 52 | 53 | User user4 = 54 | userRepository.save( 55 | User.builder() 56 | .name("username4") 57 | .email("username4@mail.com") 58 | .username("username4") 59 | .build()); 60 | 61 | addressRepository.save( 62 | Address.builder().title("addressTitle1").detail("addressDetail1").user(user1).build()); 63 | addressRepository.save( 64 | Address.builder().title("addressTitle2").detail("addressDetail2").user(user1).build()); 65 | addressRepository.save( 66 | Address.builder().title("addressTitle3").detail("addressDetail3").user(user2).build()); 67 | addressRepository.save( 68 | Address.builder().title("addressTitle4").detail("addressDetail4").user(user3).build()); 69 | addressRepository.save( 70 | Address.builder().title("addressTitle5").detail("addressDetail5").user(user4).build()); 71 | addressRepository.save( 72 | Address.builder().title("addressTitle6").detail("addressDetail6").user(user4).build()); 73 | addressRepository.save( 74 | Address.builder().title("addressTitle7").detail("addressDetail7").user(user4).build()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/constant/Constants.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.constant; 2 | 3 | public final class Constants { 4 | 5 | private Constants() {} 6 | 7 | public static final String EXCEPTION_MESSAGE_KEY = "error"; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/controller/AddressController.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import com.gucardev.springboottest.dto.AddressDTO; 4 | import com.gucardev.springboottest.dto.request.AddressRequest; 5 | import com.gucardev.springboottest.service.AddressService; 6 | import java.util.List; 7 | import javax.validation.Valid; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.DeleteMapping; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.PutMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @ConditionalOnExpression( 21 | "${address.controller.enabled:false}") 22 | @RestController 23 | @RequestMapping("/api/address") 24 | public class AddressController { 25 | 26 | private final AddressService addressService; 27 | 28 | public AddressController(AddressService addressService) { 29 | this.addressService = addressService; 30 | } 31 | 32 | @GetMapping("/user/{id}") 33 | public ResponseEntity> getAll(@PathVariable Long id) { 34 | List result = addressService.getAllByUserId(id); 35 | return ResponseEntity.ok(result); 36 | } 37 | 38 | @GetMapping("/{id}") 39 | public ResponseEntity getById(@PathVariable Long id) { 40 | return ResponseEntity.ok(addressService.getByIdDTO(id)); 41 | } 42 | 43 | @PostMapping 44 | public ResponseEntity createAddress(@RequestBody @Valid AddressRequest addressRequest) { 45 | return ResponseEntity.status(HttpStatus.CREATED).body(addressService.create(addressRequest)); 46 | } 47 | 48 | @PutMapping 49 | public ResponseEntity updateAddress(@RequestBody @Valid AddressRequest addressRequest) { 50 | return ResponseEntity.ok(addressService.update(addressRequest)); 51 | } 52 | 53 | @DeleteMapping("/{id}") 54 | public ResponseEntity deleteAddress(@PathVariable Long id) { 55 | addressService.delete(id); 56 | return ResponseEntity.ok().build(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import com.gucardev.springboottest.dto.UserDTO; 4 | import com.gucardev.springboottest.dto.request.UserRequest; 5 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 6 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 7 | import com.gucardev.springboottest.service.UserService; 8 | import io.github.resilience4j.ratelimiter.annotation.RateLimiter; 9 | import java.util.List; 10 | import javax.validation.Valid; 11 | import org.springframework.data.domain.Page; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.data.domain.Sort; 14 | import org.springframework.data.web.PageableDefault; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.web.bind.annotation.DeleteMapping; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.PathVariable; 20 | import org.springframework.web.bind.annotation.PostMapping; 21 | import org.springframework.web.bind.annotation.PutMapping; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RequestParam; 25 | import org.springframework.web.bind.annotation.RestController; 26 | 27 | @RestController 28 | @RequestMapping("/api/user") 29 | public class UserController { 30 | 31 | private final UserService userService; 32 | 33 | public UserController(UserService userService) { 34 | this.userService = userService; 35 | } 36 | 37 | @GetMapping 38 | public ResponseEntity> getAllPageable( 39 | @RequestParam(required = false) String searchTerm, 40 | @RequestParam(defaultValue = "name", required = false) String sortField, 41 | @RequestParam(defaultValue = "ASC", required = false) String sortDirection, 42 | @PageableDefault(size = 20) Pageable pageable) { 43 | Sort.Direction direction = Sort.Direction.fromString(sortDirection.toUpperCase()); 44 | Page result = userService.getAllPageable(searchTerm, sortField, direction, pageable); 45 | return ResponseEntity.ok(result); 46 | } 47 | 48 | @RateLimiter(name = "basic") 49 | @GetMapping("/mail-username") 50 | public ResponseEntity> getMailAndUsernames() { 51 | return ResponseEntity.ok(userService.getMailAndUsernames()); 52 | } 53 | 54 | @GetMapping("/{id}") 55 | public ResponseEntity getById(@PathVariable Long id) { 56 | return ResponseEntity.ok(userService.getByIdDTO(id)); 57 | } 58 | 59 | @PostMapping 60 | public ResponseEntity createUser(@RequestBody @Valid UserRequest userRequest) { 61 | return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(userRequest)); 62 | } 63 | 64 | @PutMapping 65 | public ResponseEntity updateUser(@RequestBody @Valid UserRequest userRequest) { 66 | return ResponseEntity.ok(userService.update(userRequest)); 67 | } 68 | 69 | @DeleteMapping("/{id}") 70 | public ResponseEntity updateUser(@PathVariable Long id) { 71 | userService.delete(id); 72 | return ResponseEntity.ok().build(); 73 | } 74 | 75 | @GetMapping("/different-users") 76 | public ResponseEntity> differentUsers() { 77 | return ResponseEntity.ok(userService.getDifferentUsers()); 78 | } 79 | 80 | @GetMapping("/username-length/{length}") 81 | public ResponseEntity> getById(@PathVariable Integer length) { 82 | return ResponseEntity.ok(userService.getUserNamesListWithLengthGreaterThan(length)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/AddressDTO.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class AddressDTO { 15 | 16 | private Long id; 17 | 18 | private String title; 19 | 20 | private String detail; 21 | 22 | private Long userId; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/RestPageResponse.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import org.springframework.data.domain.PageImpl; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.data.domain.Pageable; 11 | 12 | public class RestPageResponse extends PageImpl { 13 | 14 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 15 | public RestPageResponse(@JsonProperty("content") List content, 16 | @JsonProperty("number") int number, 17 | @JsonProperty("size") int size, 18 | @JsonProperty("totalElements") Long totalElements, 19 | @JsonProperty("pageable") JsonNode pageable, 20 | @JsonProperty("last") boolean last, 21 | @JsonProperty("totalPages") int totalPages, 22 | @JsonProperty("sort") JsonNode sort, 23 | @JsonProperty("first") boolean first, 24 | @JsonProperty("numberOfElements") int numberOfElements) { 25 | 26 | super(content, PageRequest.of(number, size), totalElements); 27 | } 28 | 29 | public RestPageResponse(List content, Pageable pageable, long total) { 30 | super(content, pageable, total); 31 | } 32 | 33 | public RestPageResponse(List content) { 34 | super(content); 35 | } 36 | 37 | public RestPageResponse() { 38 | super(new ArrayList<>()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserDTO { 15 | 16 | private Long id; 17 | 18 | private String username; 19 | private String email; 20 | private String name; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/converter/AddressConverter.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.converter; 2 | 3 | import com.gucardev.springboottest.dto.AddressDTO; 4 | import com.gucardev.springboottest.dto.request.AddressRequest; 5 | import com.gucardev.springboottest.model.Address; 6 | import com.gucardev.springboottest.model.User; 7 | import java.util.Optional; 8 | import java.util.function.BiFunction; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class AddressConverter { 13 | 14 | BiFunction nullable = 15 | (val, defaultVal) -> Optional.ofNullable(val).orElse(defaultVal); 16 | 17 | public Address mapToEntity(AddressRequest addressRequest) { 18 | return Address.builder() 19 | .title((String) nullable.apply(addressRequest.getTitle(), "")) 20 | .detail((String) nullable.apply(addressRequest.getDetail(), "")) 21 | .user(User.builder().id((Long) nullable.apply(addressRequest.getUserId(), -1L)).build()) 22 | .build(); 23 | } 24 | 25 | public AddressDTO mapToDTO(Address address) { 26 | return AddressDTO.builder() 27 | .id((Long) nullable.apply(address.getId(), 0L)) 28 | .title((String) nullable.apply(address.getTitle(), "")) 29 | .detail((String) nullable.apply(address.getDetail(), "")) 30 | .userId( 31 | (Long) 32 | nullable.apply(address.getUser() != null ? address.getUser().getId() : null, -1L)) 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/converter/UserConverter.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.converter; 2 | 3 | import com.gucardev.springboottest.dto.UserDTO; 4 | import com.gucardev.springboottest.dto.request.UserRequest; 5 | import com.gucardev.springboottest.model.User; 6 | import java.util.Optional; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class UserConverter { 11 | 12 | public User mapToEntity(UserRequest userRequest) { 13 | return User.builder() 14 | .username(Optional.ofNullable(userRequest.getUsername()).orElse("")) 15 | .email(Optional.ofNullable(userRequest.getEmail()).orElse("")) 16 | .name(Optional.ofNullable(userRequest.getName()).orElse("")) 17 | .build(); 18 | } 19 | 20 | public UserDTO mapToDTO(User user) { 21 | return UserDTO.builder() 22 | .id(Optional.ofNullable(user.getId()).orElse(0L)) 23 | .username(Optional.ofNullable(user.getUsername()).orElse("")) 24 | .email(Optional.ofNullable(user.getEmail()).orElse("")) 25 | .name(Optional.ofNullable(user.getName()).orElse("")) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/request/AddressRequest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.request; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | @Getter 12 | @Setter 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class AddressRequest { 17 | 18 | private Long id; 19 | 20 | @NotBlank private String title; 21 | 22 | private String detail; 23 | @NotNull private Long userId; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/dto/request/UserRequest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.request; 2 | 3 | import javax.validation.constraints.Email; 4 | import javax.validation.constraints.NotBlank; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | import org.hibernate.validator.constraints.Length; 11 | 12 | @Getter 13 | @Setter 14 | @Builder 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class UserRequest { 18 | 19 | private Long id; 20 | 21 | @NotBlank 22 | @Length(max = 30, min = 3) 23 | private String username; 24 | 25 | @Email @NotBlank private String email; 26 | @NotBlank private String name; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.exception; 2 | 3 | import com.gucardev.springboottest.constant.Constants; 4 | import io.github.resilience4j.ratelimiter.RequestNotPermitted; 5 | import java.util.AbstractMap.SimpleEntry; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import javax.validation.ConstraintViolationException; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.validation.FieldError; 12 | import org.springframework.web.bind.MethodArgumentNotValidException; 13 | import org.springframework.web.bind.annotation.ExceptionHandler; 14 | import org.springframework.web.bind.annotation.RestControllerAdvice; 15 | import org.springframework.web.context.request.WebRequest; 16 | 17 | @RestControllerAdvice 18 | public class GlobalExceptionHandler { 19 | 20 | @ExceptionHandler(MethodArgumentNotValidException.class) 21 | public final ResponseEntity> handleMethodArgumentNotValidEx( 22 | MethodArgumentNotValidException ex, WebRequest request) { 23 | return getMapResponseEntity(ex); 24 | } 25 | 26 | @ExceptionHandler(ConstraintViolationException.class) 27 | public final ResponseEntity> handleConstraintViolationEx( 28 | MethodArgumentNotValidException ex, WebRequest request) { 29 | return getMapResponseEntity(ex); 30 | } 31 | 32 | @ExceptionHandler(RuntimeException.class) 33 | public ResponseEntity> handleRuntimeException(RuntimeException ex) { 34 | return new ResponseEntity<>( 35 | new SimpleEntry<>(Constants.EXCEPTION_MESSAGE_KEY, ex.getMessage()), 36 | HttpStatus.BAD_REQUEST); 37 | } 38 | 39 | @ExceptionHandler(NotFoundException.class) 40 | public ResponseEntity> notFoundException(NotFoundException ex) { 41 | return new ResponseEntity<>( 42 | new SimpleEntry<>(Constants.EXCEPTION_MESSAGE_KEY, ex.getMessage()), HttpStatus.NOT_FOUND); 43 | } 44 | 45 | @ExceptionHandler(RequestNotPermitted.class) 46 | public ResponseEntity> handleRateLimitException( 47 | RequestNotPermitted e) { 48 | return new ResponseEntity<>( 49 | new SimpleEntry<>( 50 | Constants.EXCEPTION_MESSAGE_KEY, "Rate limit exceeded: " + e.getMessage()), 51 | HttpStatus.TOO_MANY_REQUESTS); 52 | } 53 | 54 | protected ResponseEntity> getMapResponseEntity( 55 | MethodArgumentNotValidException ex) { 56 | Map errors = new HashMap<>(); 57 | ex.getBindingResult() 58 | .getAllErrors() 59 | .forEach( 60 | x -> { 61 | String fieldName = ((FieldError) x).getField(); 62 | String errorMessage = x.getDefaultMessage(); 63 | errors.put(fieldName, errorMessage); 64 | }); 65 | return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class NotFoundException extends RuntimeException { 8 | public NotFoundException(String message) { 9 | super(message); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/model/Address.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.JoinColumn; 6 | import javax.persistence.ManyToOne; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.Setter; 11 | import lombok.experimental.SuperBuilder; 12 | 13 | @Setter 14 | @Getter 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @SuperBuilder 18 | @Entity 19 | public class Address extends BaseEntity { 20 | 21 | @Column(nullable = false) 22 | private String title; 23 | 24 | private String detail; 25 | 26 | @ManyToOne 27 | @JoinColumn(name="user_id") 28 | private User user; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/model/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model; 2 | 3 | import java.time.OffsetDateTime; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.MappedSuperclass; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | import lombok.experimental.SuperBuilder; 13 | import org.hibernate.annotations.CreationTimestamp; 14 | import org.hibernate.annotations.UpdateTimestamp; 15 | 16 | @Setter 17 | @Getter 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @SuperBuilder 21 | @MappedSuperclass 22 | public abstract class BaseEntity { 23 | 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private Long id; 27 | 28 | @CreationTimestamp private OffsetDateTime dateCreated; 29 | 30 | @UpdateTimestamp private OffsetDateTime lastUpdated; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/model/User.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model; 2 | 3 | import java.util.List; 4 | import javax.persistence.CascadeType; 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.OneToMany; 8 | import javax.persistence.Table; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.Setter; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | @Setter 16 | @Getter 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @SuperBuilder 20 | @Entity 21 | @Table(name = "user_table") 22 | public class User extends BaseEntity { 23 | 24 | @Column(unique = true) 25 | private String username; 26 | 27 | private String email; 28 | 29 | private String name; 30 | 31 | @OneToMany(mappedBy="user", cascade = CascadeType.ALL) 32 | private List
addresses; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/model/projection/MailUserNameProjection.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model.projection; 2 | 3 | public interface MailUserNameProjection { 4 | String getUsername(); 5 | 6 | String getEmail(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/model/projection/UsernameLengthProjection.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model.projection; 2 | 3 | public interface UsernameLengthProjection { 4 | String getUsername(); 5 | 6 | Long getId(); 7 | 8 | Integer getLength(); 9 | 10 | String getEmail(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/remote/RemoteUserClient.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.remote; 2 | 3 | import com.gucardev.springboottest.dto.RestPageResponse; 4 | import com.gucardev.springboottest.dto.UserDTO; 5 | import org.springframework.cloud.openfeign.FeignClient; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | 8 | @FeignClient(name = "remote-user", url = "http://localhost:3000/mock") 9 | public interface RemoteUserClient { 10 | 11 | @GetMapping("/user") 12 | RestPageResponse getUsers(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/repository/AddressRepository.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import com.gucardev.springboottest.model.Address; 4 | import java.util.List; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface AddressRepository extends JpaRepository { 8 | 9 | List
findAllByUser_Id(Long id); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/repository/UserCustomRepository.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 4 | import java.util.List; 5 | 6 | public interface UserCustomRepository { 7 | 8 | List getUserNamesListWithLengthGreaterThan(int length); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/repository/UserCustomRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 4 | import java.math.BigInteger; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | import javax.persistence.Query; 10 | import org.springframework.stereotype.Repository; 11 | 12 | @Repository 13 | public class UserCustomRepositoryImpl implements UserCustomRepository { 14 | 15 | @PersistenceContext EntityManager entityManager; 16 | 17 | @Override 18 | public List getUserNamesListWithLengthGreaterThan(int length) { 19 | String queryString = 20 | "SELECT u.username AS username, u.id AS id, LENGTH(u.username) AS length, u.email AS email " 21 | + "FROM user_table u WHERE LENGTH(u.username) > :length"; 22 | Query query = entityManager.createNativeQuery(queryString); 23 | query.setParameter("length", length); 24 | 25 | List results = query.getResultList(); 26 | 27 | return results.stream() 28 | .map( 29 | result -> 30 | new UsernameLengthProjection() { 31 | private final String username = (String) result[0]; 32 | private final Long id = ((BigInteger) result[1]).longValue(); 33 | private final Integer length = ((BigInteger) result[2]).intValue(); 34 | private final String email = (String) result[3]; 35 | 36 | @Override 37 | public String getUsername() { 38 | return username; 39 | } 40 | 41 | @Override 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | @Override 47 | public Integer getLength() { 48 | return length; 49 | } 50 | 51 | @Override 52 | public String getEmail() { 53 | return email; 54 | } 55 | }) 56 | .collect(Collectors.toList()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import com.gucardev.springboottest.model.User; 4 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 5 | import java.util.List; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.jpa.domain.Specification; 9 | import org.springframework.data.jpa.repository.JpaRepository; 10 | import org.springframework.data.jpa.repository.Query; 11 | import org.springframework.data.repository.query.Param; 12 | 13 | public interface UserRepository extends JpaRepository, UserCustomRepository { 14 | 15 | boolean existsById(Long id); 16 | 17 | boolean existsByUsernameIgnoreCase(String username); 18 | 19 | Page findAll(Specification spec, Pageable pageable); 20 | 21 | @Query( 22 | value = "select u.username as username, u.email as email from user_table u", 23 | nativeQuery = true) 24 | List findAllMailAndUserName(); 25 | 26 | @Query("SELECT u FROM User u WHERE u.username NOT IN :usernames") 27 | List findUsersNotInUsernameList(@Param("usernames") List usernames); 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/service/AddressService.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service; 2 | 3 | import com.gucardev.springboottest.dto.AddressDTO; 4 | import com.gucardev.springboottest.dto.request.AddressRequest; 5 | import com.gucardev.springboottest.model.Address; 6 | import java.util.List; 7 | 8 | public interface AddressService { 9 | 10 | List getAllByUserId(Long id); 11 | 12 | AddressDTO getByIdDTO(Long id); 13 | 14 | Address getById(Long id); 15 | 16 | AddressDTO create(AddressRequest addressRequest); 17 | 18 | AddressDTO update(AddressRequest addressRequest); 19 | 20 | void delete(Long id); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service; 2 | 3 | import com.gucardev.springboottest.dto.UserDTO; 4 | import com.gucardev.springboottest.dto.request.UserRequest; 5 | import com.gucardev.springboottest.model.User; 6 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 7 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 8 | import java.util.List; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.data.domain.Sort; 12 | 13 | public interface UserService { 14 | 15 | Page getAllPageable( 16 | String searchTerm, String sortField, Sort.Direction sortDirection, Pageable pageable); 17 | 18 | User getById(Long id); 19 | 20 | Boolean userExistsById(Long id); 21 | 22 | UserDTO getByIdDTO(Long id); 23 | 24 | UserDTO create(UserRequest userRequest); 25 | 26 | UserDTO update(UserRequest userRequest); 27 | 28 | void delete(Long id); 29 | 30 | List getUserNamesListWithLengthGreaterThan(Integer length); 31 | 32 | List getMailAndUsernames(); 33 | 34 | List getDifferentUsers(); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/service/impl/AddressServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import com.gucardev.springboottest.dto.AddressDTO; 4 | import com.gucardev.springboottest.dto.converter.AddressConverter; 5 | import com.gucardev.springboottest.dto.request.AddressRequest; 6 | import com.gucardev.springboottest.exception.NotFoundException; 7 | import com.gucardev.springboottest.model.Address; 8 | import com.gucardev.springboottest.repository.AddressRepository; 9 | import com.gucardev.springboottest.service.AddressService; 10 | import com.gucardev.springboottest.service.UserService; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | import javax.annotation.PostConstruct; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.cache.annotation.CacheConfig; 16 | import org.springframework.cache.annotation.CacheEvict; 17 | import org.springframework.cache.annotation.Cacheable; 18 | import org.springframework.scheduling.annotation.Scheduled; 19 | import org.springframework.stereotype.Service; 20 | 21 | @Service 22 | @CacheConfig(cacheNames = {"address"}) 23 | @Slf4j 24 | public class AddressServiceImpl implements AddressService { 25 | 26 | private final AddressRepository addressRepository; 27 | private final UserService userService; 28 | private final AddressConverter addressConverter; 29 | 30 | public AddressServiceImpl( 31 | AddressRepository addressRepository, 32 | UserService userService, 33 | AddressConverter addressConverter) { 34 | this.addressRepository = addressRepository; 35 | this.userService = userService; 36 | this.addressConverter = addressConverter; 37 | } 38 | 39 | /** Clear cache. */ 40 | @CacheEvict(allEntries = true) 41 | @PostConstruct 42 | @Scheduled(fixedRateString = "${caching.config.address.cache-ttl}") 43 | public void clearCache() { 44 | log.info("Caches are cleared"); 45 | } 46 | 47 | @Override 48 | @Cacheable(key = "#id") 49 | public List getAllByUserId(Long id) { 50 | if (Boolean.FALSE.equals(userService.userExistsById(id))) { 51 | throw new NotFoundException("user not found!"); 52 | } 53 | return addressRepository.findAllByUser_Id(id).stream() 54 | .map(addressConverter::mapToDTO) 55 | .collect(Collectors.toList()); 56 | } 57 | 58 | @Override 59 | @Cacheable(key = "#id") 60 | public AddressDTO getByIdDTO(Long id) { 61 | return addressConverter.mapToDTO(getById(id)); 62 | } 63 | 64 | @Override 65 | public Address getById(Long id) { 66 | return addressRepository 67 | .findById(id) 68 | .orElseThrow(() -> new NotFoundException("address not found!")); 69 | } 70 | 71 | @Override 72 | public AddressDTO create(AddressRequest addressRequest) { 73 | if (Boolean.FALSE.equals(userService.userExistsById(addressRequest.getUserId()))) { 74 | throw new NotFoundException("user not found!"); 75 | } 76 | Address address = addressConverter.mapToEntity(addressRequest); 77 | 78 | return addressConverter.mapToDTO(addressRepository.save(address)); 79 | } 80 | 81 | @Override 82 | public AddressDTO update(AddressRequest addressRequest) { 83 | Address existing = getById(addressRequest.getId()); 84 | existing.setTitle(addressRequest.getTitle()); 85 | existing.setDetail(addressRequest.getDetail()); 86 | return addressConverter.mapToDTO(addressRepository.save(existing)); 87 | } 88 | 89 | @Override 90 | public void delete(Long id) { 91 | Address existing = getById(id); 92 | addressRepository.delete(existing); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import com.gucardev.springboottest.dto.UserDTO; 4 | import com.gucardev.springboottest.dto.converter.UserConverter; 5 | import com.gucardev.springboottest.dto.request.UserRequest; 6 | import com.gucardev.springboottest.exception.NotFoundException; 7 | import com.gucardev.springboottest.model.User; 8 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 9 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 10 | import com.gucardev.springboottest.remote.RemoteUserClient; 11 | import com.gucardev.springboottest.repository.UserRepository; 12 | import com.gucardev.springboottest.service.UserService; 13 | import com.gucardev.springboottest.spesification.UserSpecifications; 14 | import java.util.List; 15 | import java.util.function.Function; 16 | import java.util.stream.Collectors; 17 | import org.springframework.data.domain.Page; 18 | import org.springframework.data.domain.Pageable; 19 | import org.springframework.data.domain.Sort; 20 | import org.springframework.data.jpa.domain.Specification; 21 | import org.springframework.stereotype.Service; 22 | 23 | @Service 24 | public class UserServiceImpl implements UserService { 25 | 26 | private final UserRepository userRepository; 27 | private final UserConverter userConverter; 28 | private final RemoteUserClient userClient; 29 | 30 | public UserServiceImpl( 31 | UserRepository userRepository, UserConverter userConverter, RemoteUserClient userClient) { 32 | this.userRepository = userRepository; 33 | this.userConverter = userConverter; 34 | this.userClient = userClient; 35 | } 36 | 37 | @Override 38 | public Page getAllPageable( 39 | String searchTerm, String sortField, Sort.Direction sortDirection, Pageable pageable) { 40 | Specification spec = 41 | Specification.where(UserSpecifications.searchByKeyword(searchTerm)) 42 | .and(UserSpecifications.sortByField(sortField, sortDirection)); 43 | return userRepository.findAll(spec, pageable).map(userConverter::mapToDTO); 44 | } 45 | 46 | @Override 47 | public User getById(Long id) { 48 | return userRepository.findById(id).orElseThrow(() -> new NotFoundException("not found!")); 49 | } 50 | 51 | @Override 52 | public Boolean userExistsById(Long id) { 53 | return userRepository.findById(id).isPresent(); 54 | } 55 | 56 | @Override 57 | public UserDTO getByIdDTO(Long id) { 58 | return userConverter.mapToDTO(getById(id)); 59 | } 60 | 61 | @Override 62 | public UserDTO create(UserRequest userRequest) { 63 | if (userRepository.existsByUsernameIgnoreCase(userRequest.getUsername())) { 64 | throw new NotFoundException("user already exists!"); 65 | } 66 | User saved = userRepository.save(userConverter.mapToEntity(userRequest)); 67 | return userConverter.mapToDTO(saved); 68 | } 69 | 70 | @Override 71 | public UserDTO update(UserRequest userRequest) { 72 | if (!userRepository.existsById(userRequest.getId())) { 73 | throw new NotFoundException("user does not exists!"); 74 | } 75 | User existing = getById(userRequest.getId()); 76 | existing.setEmail(userRequest.getEmail()); 77 | existing.setName(userRequest.getName()); 78 | User saved = userRepository.save(existing); 79 | return userConverter.mapToDTO(saved); 80 | } 81 | 82 | @Override 83 | public void delete(Long id) { 84 | if (!userRepository.existsById(id)) { 85 | throw new NotFoundException("user does not exists!"); 86 | } 87 | userRepository.deleteById(id); 88 | } 89 | 90 | @Override 91 | public List getUserNamesListWithLengthGreaterThan(Integer length) { 92 | return userRepository.getUserNamesListWithLengthGreaterThan(length); 93 | } 94 | 95 | @Override 96 | public List getMailAndUsernames() { 97 | return userRepository.findAllMailAndUserName(); 98 | } 99 | 100 | @Override 101 | public List getDifferentUsers() { 102 | List remoteUsers = userClient.getUsers().getContent(); 103 | List differentUsers = 104 | userRepository.findUsersNotInUsernameList(getUsername.apply(remoteUsers)); 105 | return differentUsers.stream().map(userConverter::mapToDTO).collect(Collectors.toList()); 106 | } 107 | 108 | private final Function, List> getUsername = 109 | value -> value.stream().map(UserDTO::getUsername).collect(Collectors.toList()); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/gucardev/springboottest/spesification/UserSpecifications.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.spesification; 2 | 3 | import com.gucardev.springboottest.model.User; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javax.persistence.criteria.Predicate; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.data.domain.Sort; 9 | import org.springframework.data.jpa.domain.Specification; 10 | 11 | public class UserSpecifications { 12 | 13 | public static Specification searchByKeyword(String searchTerm) { 14 | return (root, query, criteriaBuilder) -> { 15 | List predicates = new ArrayList<>(); 16 | 17 | if (StringUtils.isNotBlank(searchTerm)) { 18 | String likeTerm = "%" + searchTerm.toLowerCase() + "%"; 19 | predicates.add( 20 | criteriaBuilder.or( 21 | criteriaBuilder.like(criteriaBuilder.lower(root.get("username")), likeTerm), 22 | criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), likeTerm), 23 | criteriaBuilder.like(criteriaBuilder.lower(root.get("email")), likeTerm))); 24 | } 25 | 26 | // filter itself 27 | // if (userId != null) { 28 | // predicates.add(criteriaBuilder.notEqual(root.get("id"), userId)); 29 | // } 30 | 31 | return criteriaBuilder.and(predicates.toArray(new Predicate[0])); 32 | }; 33 | } 34 | 35 | public static Specification sortByField(String sortField, Sort.Direction sortDirection) { 36 | return (root, query, criteriaBuilder) -> { 37 | if (sortField.equals("name")) { 38 | if (sortDirection.isAscending()) { 39 | query.orderBy(criteriaBuilder.asc(root.get("name"))); 40 | } else { 41 | query.orderBy(criteriaBuilder.desc(root.get("name"))); 42 | } 43 | } else if (sortField.equals("username")) { 44 | if (sortDirection.isAscending()) { 45 | query.orderBy(criteriaBuilder.asc(root.get("username"))); 46 | } else { 47 | query.orderBy(criteriaBuilder.desc(root.get("username"))); 48 | } 49 | } else if (sortField.equals("email")) { 50 | if (sortDirection.isAscending()) { 51 | query.orderBy(criteriaBuilder.asc(root.get("email"))); 52 | } else { 53 | query.orderBy(criteriaBuilder.desc(root.get("email"))); 54 | } 55 | } 56 | 57 | return query.getRestriction(); 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | h2: 3 | console: 4 | enabled: 'true' 5 | datasource: 6 | url: jdbc:h2:mem:database 7 | caching: 8 | config: 9 | user: 10 | cache-ttl: '10000' 11 | cache-name: user 12 | address: 13 | cache-ttl: '10000' 14 | cache-name: address 15 | address: 16 | controller: 17 | enabled: 'true' 18 | 19 | resilience4j: 20 | ratelimiter: 21 | instances: 22 | basic: 23 | limit-for-period: 2 24 | limit-refresh-period: 8s 25 | timeout-duration: 2s -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | h2: 3 | console: 4 | enabled: 'true' 5 | datasource: 6 | url: jdbc:h2:mem:database 7 | profiles: 8 | active: dev 9 | caching: 10 | config: 11 | user: 12 | cache-ttl: '10000' 13 | cache-name: user 14 | address: 15 | cache-ttl: '10000' 16 | cache-name: address 17 | address: 18 | controller: 19 | enabled: 'true' 20 | 21 | resilience4j: 22 | ratelimiter: 23 | instances: 24 | basic: 25 | limit-for-period: 3 26 | limit-refresh-period: 1m 27 | timeout-duration: 10s -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/SpringBootTestApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootTestApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/config/CachingConfigurationPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.config; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.test.context.junit.jupiter.SpringExtension; 12 | 13 | @ExtendWith(SpringExtension.class) 14 | @SpringBootTest 15 | @EnableConfigurationProperties(value = CachingConfigurationProperties.class) 16 | @Configuration 17 | class CachingConfigurationPropertiesTest { 18 | 19 | @Autowired private CachingConfigurationProperties cachingConfigurationProperties; 20 | 21 | @Test 22 | void constructor_shouldInitializeProperties() { 23 | 24 | String userCacheName = "user"; 25 | Long userCacheTtl = 10000L; 26 | String addressCacheName = "address"; 27 | Long addressCacheTtl = 10000L; 28 | 29 | assertEquals(userCacheName, cachingConfigurationProperties.getUser().getCacheName()); 30 | assertEquals(userCacheTtl, cachingConfigurationProperties.getUser().getCacheTtl()); 31 | assertEquals(addressCacheName, cachingConfigurationProperties.getAddress().getCacheName()); 32 | assertEquals(addressCacheTtl, cachingConfigurationProperties.getAddress().getCacheTtl()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/constant/ConstantsTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.constant; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Modifier; 10 | import org.junit.jupiter.api.Test; 11 | 12 | class ConstantsTest { 13 | 14 | @Test 15 | void testPrivateConstructor() 16 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, 17 | InstantiationException { 18 | Constructor constructor = Constants.class.getDeclaredConstructor(); 19 | assertTrue(Modifier.isPrivate(constructor.getModifiers())); 20 | constructor.setAccessible(true); 21 | assertNotNull(constructor.newInstance()); 22 | } 23 | 24 | @Test 25 | void testConstants() { 26 | assertEquals("error", Constants.EXCEPTION_MESSAGE_KEY); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/controller/AddressControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import static org.hamcrest.Matchers.hasSize; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 7 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 8 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 | 12 | import com.gucardev.springboottest.dto.request.AddressRequest; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 16 | 17 | class AddressControllerIntegrationTest extends AddressControllerIntegrationTestSupport { 18 | 19 | @Test 20 | void getAll_givenUserId_returnsAddresses() throws Exception { 21 | long userId = 1L; 22 | mockMvc 23 | .perform( 24 | MockMvcRequestBuilders.get("/api/address/user/" + userId) 25 | .accept(MediaType.APPLICATION_JSON)) 26 | .andExpect(status().isOk()) 27 | .andExpect(jsonPath("$", hasSize(3))); 28 | } 29 | 30 | @Test 31 | void getById_givenValidId_ShouldReturnAddressDTO() throws Exception { 32 | mockMvc 33 | .perform(get("/api/address/{id}", 1L)) 34 | .andExpect(status().isOk()) 35 | .andExpect(jsonPath("$.id", is(1))); 36 | } 37 | 38 | @Test 39 | void getById_givenNonExistentId_ShouldReturnError() throws Exception { 40 | Long nonExistingId = 100000L; 41 | mockMvc.perform(get("/api/address/{id}", nonExistingId)).andExpect(status().is4xxClientError()); 42 | } 43 | 44 | @Test 45 | void createAddress_givenValidAddressRequest_ShouldReturnCreatedAddressDTO() throws Exception { 46 | AddressRequest addressRequest = new AddressRequest(); 47 | addressRequest.setTitle("Test title"); 48 | addressRequest.setDetail("Test detail"); 49 | addressRequest.setUserId(1L); 50 | 51 | mockMvc 52 | .perform( 53 | post("/api/address") 54 | .contentType(MediaType.APPLICATION_JSON) 55 | .content(objectMapper.writeValueAsString(addressRequest))) 56 | .andExpect(status().isCreated()) 57 | .andExpect(jsonPath("$.title", is("Test title"))) 58 | .andExpect(jsonPath("$.detail", is("Test detail"))); 59 | } 60 | 61 | @Test 62 | void createAddress_givenIncompleteAddressRequest_ShouldReturn400BadRequest() throws Exception { 63 | AddressRequest addressRequest = new AddressRequest(); 64 | addressRequest.setTitle("Test title"); 65 | 66 | mockMvc 67 | .perform( 68 | post("/api/address") 69 | .contentType(MediaType.APPLICATION_JSON) 70 | .content(objectMapper.writeValueAsString(addressRequest))) 71 | .andExpect(status().isBadRequest()); 72 | } 73 | 74 | @Test 75 | void updateAddress_givenValidAddressRequest_ShouldReturnUpdatedAddressDTO() throws Exception { 76 | AddressRequest addressRequest = new AddressRequest(); 77 | addressRequest.setId(1L); 78 | addressRequest.setTitle("Updated title"); 79 | addressRequest.setDetail("Updated detail"); 80 | addressRequest.setUserId(1L); 81 | 82 | mockMvc 83 | .perform( 84 | put("/api/address") 85 | .contentType(MediaType.APPLICATION_JSON) 86 | .content(objectMapper.writeValueAsString(addressRequest))) 87 | .andExpect(status().isOk()) 88 | .andExpect(jsonPath("$.title", is("Updated title"))) 89 | .andExpect(jsonPath("$.detail", is("Updated detail"))); 90 | } 91 | 92 | @Test 93 | void deleteAddress_givenValidId_ShouldReturnStatusOk() throws Exception { 94 | mockMvc.perform(delete("/api/address/{id}", 1L)).andExpect(status().isOk()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/controller/AddressControllerIntegrationTestSupport.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.gucardev.springboottest.dto.UserDTO; 5 | import com.gucardev.springboottest.dto.request.AddressRequest; 6 | import com.gucardev.springboottest.dto.request.UserRequest; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.TestInstance; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.test.annotation.DirtiesContext; 14 | import org.springframework.test.annotation.DirtiesContext.ClassMode; 15 | import org.springframework.test.web.servlet.MockMvc; 16 | import org.springframework.test.web.servlet.MvcResult; 17 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 18 | 19 | @SpringBootTest 20 | @AutoConfigureMockMvc 21 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 22 | @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) 23 | abstract class AddressControllerIntegrationTestSupport { 24 | 25 | @Autowired protected MockMvc mockMvc; 26 | 27 | @Autowired protected ObjectMapper objectMapper; 28 | 29 | @BeforeEach 30 | void setupBeforeEach() throws Exception { 31 | 32 | for (int i = 0; i < 2; i++) { 33 | UserRequest userRequest = 34 | UserRequest.builder() 35 | .email("example" + (i + 1) + "@mail.com") 36 | .username("username" + (i + 1)) 37 | .name("name" + (i + 1)) 38 | .build(); 39 | MvcResult mvcResult = 40 | mockMvc 41 | .perform( 42 | MockMvcRequestBuilders.post("/api/user") 43 | .contentType(MediaType.APPLICATION_JSON) 44 | .content(objectMapper.writeValueAsString(userRequest)) 45 | .accept(MediaType.APPLICATION_JSON)) 46 | .andReturn(); 47 | 48 | String responseBody = mvcResult.getResponse().getContentAsString(); 49 | UserDTO createdUser = objectMapper.readValue(responseBody, UserDTO.class); 50 | 51 | // Create addresses for the user 52 | for (int j = 0; j < 3; j++) { 53 | AddressRequest addressRequest = 54 | AddressRequest.builder() 55 | .title("Address " + (j + 1) + " for user " + (i + 1)) 56 | .detail("Detail " + (j + 1) + " for address " + (j + 1) + " of user " + (i + 1)) 57 | .userId(createdUser.getId()) 58 | .build(); 59 | 60 | mockMvc.perform( 61 | MockMvcRequestBuilders.post("/api/address") 62 | .contentType(MediaType.APPLICATION_JSON) 63 | .content(objectMapper.writeValueAsString(addressRequest)) 64 | .accept(MediaType.APPLICATION_JSON)); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/controller/UserControllerIntegrationTestSupport.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 5 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; 6 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 7 | 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.github.tomakehurst.wiremock.WireMockServer; 10 | import com.github.tomakehurst.wiremock.client.WireMock; 11 | import com.gucardev.springboottest.dto.RestPageResponse; 12 | import com.gucardev.springboottest.dto.UserDTO; 13 | import com.gucardev.springboottest.dto.request.UserRequest; 14 | import com.gucardev.springboottest.repository.UserRepository; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import org.junit.jupiter.api.AfterAll; 18 | import org.junit.jupiter.api.AfterEach; 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.TestInstance; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 23 | import org.springframework.boot.test.context.SpringBootTest; 24 | import org.springframework.http.MediaType; 25 | import org.springframework.test.annotation.DirtiesContext; 26 | import org.springframework.test.annotation.DirtiesContext.ClassMode; 27 | import org.springframework.test.web.servlet.MockMvc; 28 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 29 | 30 | @SpringBootTest 31 | @AutoConfigureMockMvc 32 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 33 | @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) 34 | abstract class UserControllerIntegrationTestSupport { 35 | 36 | WireMockServer wireMockServer = new WireMockServer(3000); 37 | 38 | @Autowired protected MockMvc mockMvc; 39 | 40 | @Autowired protected ObjectMapper objectMapper; 41 | 42 | @Autowired protected UserRepository userRepository; 43 | 44 | @BeforeEach 45 | void setupBeforeEach() throws Exception { 46 | 47 | for (int i = 0; i < 5; i++) { 48 | UserRequest userRequest = 49 | UserRequest.builder() 50 | .email("example" + (i + 1) + "@mail.com") 51 | .username("username" + (i + 1)) 52 | .name("name" + (i + 1)) 53 | .build(); 54 | mockMvc.perform( 55 | MockMvcRequestBuilders.post("/api/user") 56 | .contentType(MediaType.APPLICATION_JSON) 57 | .content(objectMapper.writeValueAsString(userRequest)) 58 | .accept(MediaType.APPLICATION_JSON)); 59 | } 60 | 61 | // wiremock setup 62 | 63 | wireMockServer.start(); 64 | 65 | List mockUserDTOs = 66 | Arrays.asList( 67 | new UserDTO(1L, "User1", "user1@example.com", "username1"), 68 | new UserDTO(2L, "User2", "user2@example.com", "username2")); 69 | 70 | RestPageResponse pageResponse = new RestPageResponse<>(mockUserDTOs); 71 | 72 | String jsonResponse = objectMapper.writeValueAsString(pageResponse); 73 | 74 | WireMock.configureFor("localhost", wireMockServer.port()); 75 | stubFor( 76 | get(urlEqualTo("/mock/user")) 77 | .willReturn( 78 | aResponse().withHeader("Content-Type", "application/json").withBody(jsonResponse))); 79 | } 80 | 81 | @AfterEach 82 | void tearDownAfterEach() { 83 | // userRepository.deleteAll(); 84 | // wireMockServer.stop(); 85 | } 86 | 87 | @AfterAll 88 | void tearDownAfterAll() { 89 | wireMockServer.stop(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/controller/UserControllerUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.ArgumentMatchers.anyLong; 6 | import static org.mockito.Mockito.when; 7 | 8 | import com.gucardev.springboottest.dto.UserDTO; 9 | import com.gucardev.springboottest.dto.request.UserRequest; 10 | import com.gucardev.springboottest.service.UserService; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.mockito.Mock; 15 | import org.mockito.junit.jupiter.MockitoExtension; 16 | import org.springframework.data.domain.Page; 17 | import org.springframework.data.domain.PageRequest; 18 | import org.springframework.data.domain.Pageable; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.http.ResponseEntity; 21 | 22 | @ExtendWith(MockitoExtension.class) 23 | class UserControllerUnitTest { 24 | 25 | @Mock private UserService userService; 26 | private UserController userController; 27 | 28 | @BeforeEach 29 | public void setup() { 30 | // MockitoAnnotations.openMocks(this); // or use class annotation 31 | userController = new UserController(userService); 32 | } 33 | 34 | @Test 35 | void getAllPageable_givenSortFieldAndSortDirection_returnsUsers() { 36 | Pageable pageable = PageRequest.of(0, 20); 37 | Page userDTOPage = Page.empty(pageable); 38 | when(userService.getAllPageable(any(), any(), any(), any())).thenReturn(userDTOPage); 39 | ResponseEntity> response = 40 | userController.getAllPageable("searchTerm", "name", "ASC", pageable); 41 | assertEquals(HttpStatus.OK, response.getStatusCode()); 42 | assertEquals(userDTOPage, response.getBody()); 43 | } 44 | 45 | @Test 46 | void getById_givenUserId_returnsUser() { 47 | UserDTO userDTO = new UserDTO(); 48 | when(userService.getByIdDTO(anyLong())).thenReturn(userDTO); 49 | ResponseEntity response = userController.getById(1L); 50 | assertEquals(HttpStatus.OK, response.getStatusCode()); 51 | assertEquals(userDTO, response.getBody()); 52 | } 53 | 54 | @Test 55 | void createUser_givenUserRequest_createsUser() { 56 | UserDTO userDTO = new UserDTO(); 57 | when(userService.create(any())).thenReturn(userDTO); 58 | ResponseEntity response = userController.createUser(any()); 59 | assertEquals(HttpStatus.CREATED, response.getStatusCode()); 60 | assertEquals(userDTO, response.getBody()); 61 | } 62 | 63 | @Test 64 | void updateUser_givenUserRequest_updatesUser() { 65 | UserDTO userDTO = new UserDTO(); 66 | when(userService.update(any())).thenReturn(userDTO); 67 | ResponseEntity response = userController.updateUser(any(UserRequest.class)); 68 | assertEquals(HttpStatus.OK, response.getStatusCode()); 69 | assertEquals(userDTO, response.getBody()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/controller/UserControllerUserControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.controller; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 7 | 8 | import com.fasterxml.jackson.core.type.TypeReference; 9 | import com.gucardev.springboottest.dto.RestPageResponse; 10 | import com.gucardev.springboottest.dto.UserDTO; 11 | import com.gucardev.springboottest.dto.request.UserRequest; 12 | import java.util.List; 13 | import java.util.concurrent.TimeUnit; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.test.web.servlet.MvcResult; 17 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 18 | 19 | class UserControllerUserControllerIntegrationTest extends UserControllerIntegrationTestSupport { 20 | 21 | @Test 22 | void searchUsers_givenSortFieldAndSortDirection_returnsUsers() throws Exception { 23 | MvcResult mvcResult = 24 | mockMvc 25 | .perform( 26 | MockMvcRequestBuilders.get("/api/user") 27 | .param("sortField", "username") 28 | .param("sortDirection", "ASC") 29 | .accept(MediaType.APPLICATION_JSON)) 30 | .andExpect(status().isOk()) 31 | .andReturn(); 32 | 33 | String content = mvcResult.getResponse().getContentAsString(); 34 | RestPageResponse userDTOS = 35 | objectMapper.readValue(content, new TypeReference>() {}); 36 | assertEquals(userDTOS.getContent().get(0).getUsername(), "username1"); 37 | } 38 | 39 | @Test 40 | void getMailAndUsernames_returnsMailAndUsernamesWithRateLimiter() throws Exception { 41 | for (int i = 0; i < 2; i++) { 42 | mockMvc 43 | .perform(MockMvcRequestBuilders.get("/api/user/mail-username")) 44 | .andExpect(status().isOk()); 45 | } 46 | 47 | mockMvc 48 | .perform(MockMvcRequestBuilders.get("/api/user/mail-username")) 49 | .andExpect(status().isTooManyRequests()); 50 | 51 | TimeUnit.SECONDS.sleep(10); 52 | 53 | mockMvc 54 | .perform(MockMvcRequestBuilders.get("/api/user/mail-username")) 55 | .andExpect(status().isOk()); 56 | } 57 | 58 | @Test 59 | void getById_givenUserId_returnsUser() throws Exception { 60 | long userId = 1L; 61 | MvcResult mvcResult = 62 | mockMvc 63 | .perform( 64 | MockMvcRequestBuilders.get("/api/user/" + userId) 65 | .accept(MediaType.APPLICATION_JSON)) 66 | .andExpect(status().isOk()) 67 | .andReturn(); 68 | 69 | String content = mvcResult.getResponse().getContentAsString(); 70 | UserDTO userDTO = objectMapper.readValue(content, UserDTO.class); 71 | assertEquals(userDTO.getUsername(), "username1"); 72 | } 73 | 74 | @Test 75 | void createUser_givenUserRequest_createsUser() throws Exception { 76 | UserRequest userRequest = 77 | UserRequest.builder() 78 | .id(1L) 79 | .username("username_new") 80 | .name("name_new") 81 | .email("email_new@mail.com") 82 | .build(); 83 | MvcResult mvcResult = 84 | mockMvc 85 | .perform( 86 | MockMvcRequestBuilders.post("/api/user") 87 | .contentType(MediaType.APPLICATION_JSON) 88 | .content(objectMapper.writeValueAsString(userRequest)) 89 | .accept(MediaType.APPLICATION_JSON)) 90 | .andExpect(status().isCreated()) 91 | .andReturn(); 92 | 93 | String content = mvcResult.getResponse().getContentAsString(); 94 | UserDTO userDTO = objectMapper.readValue(content, UserDTO.class); 95 | assertEquals(userDTO.getUsername(), userRequest.getUsername()); 96 | } 97 | 98 | @Test 99 | void updateUser_givenUserRequest_updatesUser() throws Exception { 100 | 101 | UserRequest userRequest = 102 | UserRequest.builder() 103 | .id(1L) 104 | .username("username1_update") 105 | .name("name1_update") 106 | .email("email1_update@mail.com") 107 | .build(); 108 | MvcResult mvcResult = 109 | mockMvc 110 | .perform( 111 | MockMvcRequestBuilders.put("/api/user") 112 | .contentType(MediaType.APPLICATION_JSON) 113 | .content(objectMapper.writeValueAsString(userRequest)) 114 | .accept(MediaType.APPLICATION_JSON)) 115 | .andExpect(status().isOk()) 116 | .andReturn(); 117 | 118 | String content = mvcResult.getResponse().getContentAsString(); 119 | UserDTO userDTO = objectMapper.readValue(content, UserDTO.class); 120 | assertNotEquals(userDTO.getUsername(), userRequest.getUsername()); 121 | assertEquals(userDTO.getName(), userRequest.getName()); 122 | assertEquals(userDTO.getEmail(), userRequest.getEmail()); 123 | } 124 | 125 | @Test 126 | void deleteById_givenUserId_deletesUser() throws Exception { 127 | long userId = 1L; 128 | mockMvc 129 | .perform( 130 | MockMvcRequestBuilders.delete("/api/user/" + userId).accept(MediaType.APPLICATION_JSON)) 131 | .andExpect(status().isOk()); 132 | } 133 | 134 | @Test 135 | void differentUsers_returnsMultipleUsers() throws Exception { 136 | MvcResult mvcResult = 137 | mockMvc 138 | .perform( 139 | MockMvcRequestBuilders.get("/api/user/different-users") 140 | .accept(MediaType.APPLICATION_JSON)) 141 | .andExpect(status().isOk()) 142 | .andReturn(); 143 | 144 | String content = mvcResult.getResponse().getContentAsString(); 145 | List userDTOS = objectMapper.readValue(content, new TypeReference>() {}); 146 | assertTrue(userDTOS.size() > 0); 147 | } 148 | 149 | @Test 150 | void getMailAndUsernames_returnsMailAndUsernames() throws Exception { 151 | MvcResult mvcResult = 152 | mockMvc 153 | .perform( 154 | MockMvcRequestBuilders.get("/api/user/mail-username") 155 | .accept(MediaType.APPLICATION_JSON)) 156 | .andExpect(status().isOk()) 157 | .andReturn(); 158 | } 159 | 160 | @Test 161 | void getUsernameLength_givenLength_returnsUsernamesWithLength() throws Exception { 162 | int length = 5; 163 | MvcResult mvcResult = 164 | mockMvc 165 | .perform( 166 | MockMvcRequestBuilders.get("/api/user/username-length/" + length) 167 | .accept(MediaType.APPLICATION_JSON)) 168 | .andExpect(status().isOk()) 169 | .andReturn(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/dto/converter/UserConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.converter; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.gucardev.springboottest.dto.UserDTO; 6 | import com.gucardev.springboottest.dto.request.UserRequest; 7 | import com.gucardev.springboottest.model.User; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class UserConverterTest { 12 | 13 | private UserConverter userConverter; 14 | 15 | @BeforeEach 16 | void setUp() { 17 | userConverter = new UserConverter(); 18 | } 19 | 20 | @Test 21 | void mapToEntityTest() { 22 | UserRequest userRequest = new UserRequest(); 23 | userRequest.setUsername("username"); 24 | userRequest.setEmail("email@test.com"); 25 | userRequest.setName("Test User"); 26 | 27 | User user = userConverter.mapToEntity(userRequest); 28 | 29 | assertEquals(userRequest.getUsername(), user.getUsername()); 30 | assertEquals(userRequest.getEmail(), user.getEmail()); 31 | assertEquals(userRequest.getName(), user.getName()); 32 | } 33 | 34 | @Test 35 | void mapToDTOTest() { 36 | User user = new User(); 37 | user.setId(1L); 38 | user.setUsername("username"); 39 | user.setEmail("email@test.com"); 40 | user.setName("Test User"); 41 | 42 | UserDTO userDTO = userConverter.mapToDTO(user); 43 | 44 | assertEquals(user.getId(), userDTO.getId()); 45 | assertEquals(user.getUsername(), userDTO.getUsername()); 46 | assertEquals(user.getEmail(), userDTO.getEmail()); 47 | assertEquals(user.getName(), userDTO.getName()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/dto/request/UserRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.dto.request; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Set; 6 | import javax.validation.ConstraintViolation; 7 | import javax.validation.Validation; 8 | import javax.validation.Validator; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.CsvSource; 11 | import org.junit.platform.commons.util.StringUtils; 12 | 13 | class UserRequestTest { 14 | 15 | private final Validator validator; 16 | 17 | UserRequestTest() { 18 | validator = Validation.buildDefaultValidatorFactory().getValidator(); 19 | } 20 | 21 | @ParameterizedTest 22 | @CsvSource({ 23 | "1, testUsername, test@mail.com, testName, false", 24 | "2, ab, test@mail.com, testName, true", 25 | "3, testUsername, invalidEmail, testName, true", 26 | "4, testUsername, test@mail.com, , true", 27 | " , testUsername, test@mail.com, testName, false", 28 | " ,testUsernameToooooooooooooooooooooooooooooLong, test@mail.com, testName, true", 29 | " , , test@mail.com, testName, true", 30 | " , testUsername, , testName, true", 31 | " , testUsername, test@mail.com, , true" 32 | }) 33 | void testUserRequest( 34 | String idInput, String username, String email, String name, boolean hasViolations) { 35 | Long id = StringUtils.isBlank(idInput) ? 1L : Long.parseLong(idInput); 36 | 37 | UserRequest userRequest = 38 | UserRequest.builder().id(id).username(username).email(email).name(name).build(); 39 | 40 | Set> violations = validator.validate(userRequest); 41 | assertEquals(hasViolations, !violations.isEmpty()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/exception/GlobalExceptionHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.exception; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.gucardev.springboottest.constant.Constants; 6 | import java.util.AbstractMap.SimpleEntry; 7 | import java.util.Objects; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | class GlobalExceptionHandlerTest { 17 | 18 | private GlobalExceptionHandler globalExceptionHandler; 19 | 20 | private RuntimeException runtimeException; 21 | private NotFoundException notFoundException; 22 | 23 | @BeforeEach 24 | void setUp() { 25 | globalExceptionHandler = new GlobalExceptionHandler(); 26 | runtimeException = new RuntimeException("Test Runtime Exception"); 27 | notFoundException = new NotFoundException("Test Not Found Exception"); 28 | } 29 | 30 | @Test 31 | void handleRuntimeExceptionTest() { 32 | ResponseEntity> responseEntity = 33 | globalExceptionHandler.handleRuntimeException(runtimeException); 34 | assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); 35 | assertEquals( 36 | "Test Runtime Exception", Objects.requireNonNull(responseEntity.getBody()).getValue()); 37 | assertEquals(Constants.EXCEPTION_MESSAGE_KEY, responseEntity.getBody().getKey()); 38 | } 39 | 40 | @Test 41 | void notFoundExceptionTest() { 42 | ResponseEntity> responseEntity = 43 | globalExceptionHandler.notFoundException(notFoundException); 44 | assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode()); 45 | assertEquals( 46 | "Test Not Found Exception", Objects.requireNonNull(responseEntity.getBody()).getValue()); 47 | assertEquals(Constants.EXCEPTION_MESSAGE_KEY, responseEntity.getBody().getKey()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/exception/NotFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.exception; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class NotFoundExceptionTest { 8 | 9 | @Test 10 | void testNotFoundException() { 11 | String expectedMessage = "message"; 12 | NotFoundException ex = new NotFoundException(expectedMessage); 13 | assertEquals(expectedMessage, ex.getMessage()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/model/AddressTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class AddressTest { 8 | @Test 9 | void testAddressGetterSetter() { 10 | User user = new User(); 11 | user.setUsername("testUsername"); 12 | user.setEmail("testEmail"); 13 | user.setName("testName"); 14 | 15 | Address address = new Address(); 16 | address.setTitle("Test title"); 17 | address.setDetail("Test detail"); 18 | address.setUser(user); 19 | 20 | assertEquals("Test title", address.getTitle()); 21 | assertEquals("Test detail", address.getDetail()); 22 | assertEquals(user, address.getUser()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/model/UserTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.model; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class UserTest { 10 | 11 | @Test 12 | void testUserGetterSetter() { 13 | User user = new User(); 14 | Address address = new Address(); 15 | 16 | List
addresses = Collections.singletonList(address); 17 | 18 | user.setUsername("testUsername"); 19 | user.setEmail("testEmail"); 20 | user.setName("testName"); 21 | user.setAddresses(addresses); 22 | 23 | assertEquals("testUsername", user.getUsername()); 24 | assertEquals("testEmail", user.getEmail()); 25 | assertEquals("testName", user.getName()); 26 | assertEquals(addresses, user.getAddresses()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/repository/AddressRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.gucardev.springboottest.model.Address; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 11 | import org.springframework.test.context.jdbc.Sql; 12 | 13 | @DataJpaTest 14 | @Sql("classpath:db/addressData.sql") 15 | class AddressRepositoryTest { 16 | 17 | @Autowired private AddressRepository addressRepository; 18 | 19 | @Test 20 | void testFindAll() { 21 | List
addresses = addressRepository.findAll(); 22 | assertThat(addresses).hasSize(3); 23 | } 24 | 25 | @Test 26 | void testFindAllByUserId() { 27 | List
addresses = addressRepository.findAllByUser_Id(1L); 28 | assertThat(addresses).hasSize(2); 29 | } 30 | 31 | @Test 32 | void testFindById() { 33 | Optional
address = addressRepository.findById(1L); 34 | assertThat(address).isPresent(); 35 | assertThat(address.get().getTitle()).isEqualTo("Home"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/repository/UserRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.repository; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import com.gucardev.springboottest.model.User; 8 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 9 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 10 | import com.gucardev.springboottest.spesification.UserSpecifications; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.params.ParameterizedTest; 16 | import org.junit.jupiter.params.provider.CsvSource; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 19 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 20 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 21 | import org.springframework.data.domain.Page; 22 | import org.springframework.data.domain.PageRequest; 23 | import org.springframework.data.domain.Pageable; 24 | import org.springframework.data.domain.Sort; 25 | import org.springframework.data.jpa.domain.Specification; 26 | 27 | @DataJpaTest 28 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 29 | class UserRepositoryTest { 30 | 31 | @Autowired private TestEntityManager entityManager; 32 | 33 | @Autowired private UserRepository userRepository; 34 | 35 | private User user1, user2, user3, user4; 36 | 37 | @BeforeEach 38 | public void setup() { 39 | 40 | user1 = User.builder().username("user1").name("name1").email("email1@test.com").build(); 41 | user2 = User.builder().username("username2").name("name2").email("email2@test.com").build(); 42 | user3 = User.builder().username("username3").name("name3").email("email3@test.com").build(); 43 | user4 = User.builder().username("username4").name("name4").email("email4@test.com").build(); 44 | 45 | entityManager.persist(user1); 46 | entityManager.persist(user2); 47 | entityManager.persist(user3); 48 | entityManager.persist(user4); 49 | entityManager.flush(); 50 | } 51 | 52 | @Test 53 | void searchByKeyword_givenKeyword_returnMatchingUsers() { 54 | Specification spec = UserSpecifications.searchByKeyword("user1"); 55 | Pageable pageable = PageRequest.of(0, 10); 56 | 57 | Page users = userRepository.findAll(spec, pageable); 58 | 59 | assertEquals(1, users.getContent().size()); 60 | assertEquals("user1", users.getContent().get(0).getUsername()); 61 | } 62 | 63 | @ParameterizedTest 64 | @CsvSource({"asc,0,3", "desc, 3,0"}) 65 | void sortByField_givenSortDirectionAndField_returnSortedUsers( 66 | String sortDir, int order1, int order2) { 67 | Sort.Direction direction = Sort.Direction.fromString(sortDir.toUpperCase()); 68 | 69 | Specification spec = UserSpecifications.sortByField("username", direction); 70 | Pageable pageable = PageRequest.of(0, 10); 71 | 72 | Page users = userRepository.findAll(spec, pageable); 73 | 74 | assertEquals(4, users.getContent().size()); 75 | assertEquals("user1", users.getContent().get(order1).getUsername()); 76 | assertEquals("username4", users.getContent().get(order2).getUsername()); 77 | } 78 | 79 | @Test 80 | void existsByUsernameIgnoreCase_givenExistingUsername_returnTrue() { 81 | boolean exists = userRepository.existsByUsernameIgnoreCase(user1.getUsername()); 82 | assertTrue(exists); 83 | } 84 | 85 | @Test 86 | void existsByUsernameIgnoreCase_givenNonExistingUsername_returnFalse() { 87 | boolean exists = userRepository.existsByUsernameIgnoreCase("non-existing-username"); 88 | assertFalse(exists); 89 | } 90 | 91 | @Test 92 | void existsById_givenExistingId_returnTrue() { 93 | boolean exists = userRepository.existsById(user1.getId()); 94 | assertTrue(exists); 95 | } 96 | 97 | @Test 98 | void existsById_givenNonExistingId_returnFalse() { 99 | boolean exists = userRepository.existsById(-1L); 100 | assertFalse(exists); 101 | } 102 | 103 | @Test 104 | void getUserNamesListWithLengthGreaterThan_givenLength_returnUsernamesWithLengthGreaterThan() { 105 | List users = userRepository.getUserNamesListWithLengthGreaterThan(8); 106 | 107 | assertEquals(3, users.size()); 108 | assertEquals("username2", users.get(0).getUsername()); 109 | assertEquals("username3", users.get(1).getUsername()); 110 | assertEquals("username4", users.get(2).getUsername()); 111 | } 112 | 113 | @Test 114 | void findAllMailAndUserName_givenNoCondition_returnMailAndUsernames() { 115 | List users = userRepository.findAllMailAndUserName(); 116 | 117 | assertEquals(4, users.size()); 118 | 119 | assertEquals("email1@test.com", users.get(0).getEmail()); 120 | assertEquals("user1", users.get(0).getUsername()); 121 | 122 | assertEquals("email2@test.com", users.get(1).getEmail()); 123 | assertEquals("username2", users.get(1).getUsername()); 124 | 125 | assertEquals("email3@test.com", users.get(2).getEmail()); 126 | assertEquals("username3", users.get(2).getUsername()); 127 | 128 | assertEquals("email4@test.com", users.get(3).getEmail()); 129 | assertEquals("username4", users.get(3).getUsername()); 130 | } 131 | 132 | @Test 133 | void findUsersNotInUsernameList_givenUsernameList_returnNotInListUsers() { 134 | List sameUsernames = 135 | Arrays.asList(user1.getUsername(), user2.getUsername(), user4.getUsername()); 136 | List actual = userRepository.findUsersNotInUsernameList(sameUsernames); 137 | assertEquals(1,actual.size()); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/service/impl/AddressServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | import static org.mockito.ArgumentMatchers.any; 6 | import static org.mockito.ArgumentMatchers.anyLong; 7 | import static org.mockito.Mockito.doReturn; 8 | import static org.mockito.Mockito.doThrow; 9 | import static org.mockito.Mockito.never; 10 | import static org.mockito.Mockito.times; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.Mockito.when; 13 | 14 | import ch.qos.logback.classic.Level; 15 | import ch.qos.logback.classic.Logger; 16 | import ch.qos.logback.classic.spi.ILoggingEvent; 17 | import ch.qos.logback.core.read.ListAppender; 18 | import com.gucardev.springboottest.dto.AddressDTO; 19 | import com.gucardev.springboottest.dto.converter.AddressConverter; 20 | import com.gucardev.springboottest.model.Address; 21 | import com.gucardev.springboottest.repository.AddressRepository; 22 | import com.gucardev.springboottest.service.UserService; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | import java.util.Optional; 26 | import org.junit.jupiter.api.BeforeEach; 27 | import org.junit.jupiter.api.Test; 28 | import org.junit.jupiter.api.extension.ExtendWith; 29 | import org.junit.jupiter.params.ParameterizedTest; 30 | import org.junit.jupiter.params.provider.CsvSource; 31 | import org.mockito.Mock; 32 | import org.mockito.Mockito; 33 | import org.mockito.junit.jupiter.MockitoExtension; 34 | import org.slf4j.LoggerFactory; 35 | 36 | @ExtendWith(MockitoExtension.class) 37 | class AddressServiceTest extends AddressServiceTestSupport { 38 | 39 | @Mock private AddressRepository addressRepository; 40 | @Mock private AddressConverter addressConverter; 41 | @Mock private UserService userService; 42 | 43 | private AddressServiceImpl addressService; 44 | 45 | @BeforeEach 46 | void setup() { 47 | addressService = 48 | Mockito.spy(new AddressServiceImpl(addressRepository, userService, addressConverter)); 49 | setupTestData(); 50 | } 51 | 52 | @Test 53 | void getAllByUserId_givenExistingUserId_ReturnAddressDTO() { 54 | when(userService.userExistsById(any())).thenReturn(Boolean.TRUE); 55 | 56 | when(addressConverter.mapToDTO(address1)).thenReturn(addressDto1); 57 | when(addressConverter.mapToDTO(address2)).thenReturn(addressDto2); 58 | 59 | when(addressRepository.findAllByUser_Id(anyLong())) 60 | .thenReturn(Arrays.asList(address1, address2)); 61 | 62 | List actual = addressService.getAllByUserId(anyLong()); 63 | assertEquals(Arrays.asList(addressDto1, addressDto2), actual); 64 | assertEquals(2, actual.size()); 65 | verify(addressRepository, times(1)).findAllByUser_Id(anyLong()); 66 | } 67 | 68 | @Test 69 | void getAllByUserId_givenNonExistingUserId_ThrowException() { 70 | when(userService.userExistsById(any())).thenReturn(Boolean.FALSE); 71 | assertThrows(RuntimeException.class, () -> addressService.getAllByUserId(anyLong())); 72 | } 73 | 74 | @ParameterizedTest 75 | @CsvSource({"1,true", "2,false"}) 76 | void getByIdDTO_givenId_ReturnAddressDTOorThrowException(Long id, Boolean isExisting) { 77 | if (isExisting) { 78 | doReturn(address1).when(addressService).getById(id); 79 | when(addressConverter.mapToDTO(address1)).thenReturn(addressDto1); 80 | AddressDTO actual = addressService.getByIdDTO(id); 81 | assertEquals(addressDto1, actual); 82 | verify(addressConverter, times(1)).mapToDTO(address1); 83 | 84 | } else { 85 | doThrow(new RuntimeException()).when(addressService).getById(id); 86 | assertThrows(RuntimeException.class, () -> addressService.getByIdDTO(id)); 87 | verify(addressConverter, never()).mapToDTO(any()); 88 | } 89 | } 90 | 91 | @ParameterizedTest 92 | @CsvSource({"1,true", "2,false"}) 93 | void getById_givenId_ReturnAddressOrThrowException2(Long id, Boolean isExisting) { 94 | if (isExisting) { 95 | when(addressRepository.findById(id)).thenReturn(Optional.of(address1)); 96 | Address actual = addressService.getById(id); 97 | assertEquals(address1, actual); 98 | 99 | } else { 100 | when(addressRepository.findById(id)).thenReturn(Optional.empty()); 101 | assertThrows(RuntimeException.class, () -> addressService.getById(id)); 102 | } 103 | } 104 | 105 | @Test 106 | void create_givenAddressRequest_ReturnAddressDTO() { 107 | when(userService.userExistsById(any())).thenReturn(true); 108 | when(addressConverter.mapToEntity(addressRequest)).thenReturn(address1); 109 | 110 | when(addressRepository.save(address1)).thenReturn(address1); 111 | when(addressConverter.mapToDTO(address1)).thenReturn(addressDto1); 112 | 113 | AddressDTO actual = addressService.create(addressRequest); 114 | 115 | assertEquals(addressDto1, actual); 116 | } 117 | 118 | @Test 119 | void update_givenAddressRequest_ReturnAddressDTO() { 120 | doReturn(address1).when(addressService).getById(any()); 121 | when(addressConverter.mapToDTO(any(Address.class))).thenReturn(updatedAddressDto); 122 | when(addressRepository.save(any(Address.class))).thenReturn(updatedAddress); 123 | 124 | AddressDTO actual = addressService.update(addressRequest); 125 | 126 | assertEquals(updatedAddressDto, actual); 127 | } 128 | 129 | @Test 130 | void update_givenNonExistentAddressRequest_ThrowException() { 131 | doThrow(new RuntimeException()).when(addressService).getById(any()); 132 | assertThrows(RuntimeException.class, () -> addressService.update(addressRequest)); 133 | } 134 | 135 | @Test 136 | void delete_givenIdExists_delete() { 137 | doReturn(address1).when(addressService).getById(address1.getId()); 138 | addressService.delete(address1.getId()); 139 | verify(addressRepository).delete(address1); 140 | } 141 | 142 | @Test 143 | void clearCache_givenNothing_returnNothing() { 144 | Logger logger = (Logger) LoggerFactory.getLogger(AddressServiceImpl.class); 145 | ListAppender listAppender = new ListAppender<>(); 146 | listAppender.start(); 147 | logger.addAppender(listAppender); 148 | 149 | addressService.clearCache(); 150 | 151 | List logsList = listAppender.list; 152 | 153 | assertEquals("Caches are cleared", logsList.get(0).getMessage()); 154 | assertEquals(Level.INFO, logsList.get(0).getLevel()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/service/impl/AddressServiceTestSupport.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import com.gucardev.springboottest.dto.AddressDTO; 4 | import com.gucardev.springboottest.dto.UserDTO; 5 | import com.gucardev.springboottest.dto.request.AddressRequest; 6 | import com.gucardev.springboottest.model.Address; 7 | import com.gucardev.springboottest.model.User; 8 | 9 | public abstract class AddressServiceTestSupport { 10 | 11 | protected User user1, user2; 12 | protected UserDTO userDto1; 13 | 14 | protected Address address1, address2, address3, existingAddress, updatedAddress; 15 | protected AddressDTO addressDto1, addressDto2, addressDto3, updatedAddressDto; 16 | protected AddressRequest addressRequest; 17 | 18 | void setupTestData() { 19 | user1 = createUser(1L, "User1", "user1@test.com", "user1"); 20 | user2 = createUser(2L, "User2", "user2@test.com", "user2"); 21 | userDto1 = createUserDto(user1.getId(), user1.getName(), user1.getEmail(), user1.getUsername()); 22 | 23 | address1 = createAddress(1L, "address title 1", "address detail 1", user1); 24 | address2 = createAddress(2L, "address title 2", "address detail 2", user1); 25 | address3 = createAddress(3L, "address title 3", "address detail 3", user2); 26 | 27 | updatedAddress = 28 | createAddress( 29 | 1L, 30 | address1.getTitle() + "_updated", 31 | address1.getDetail() + "_updated", 32 | address1.getUser()); 33 | 34 | updatedAddressDto = 35 | createAddressDTO( 36 | 1L, updatedAddress.getTitle(), updatedAddress.getDetail(), updatedAddress.getUser()); 37 | 38 | addressDto1 = 39 | createAddressDTO(1L, address1.getTitle(), address1.getDetail(), address1.getUser()); 40 | addressDto2 = 41 | createAddressDTO(2L, address2.getTitle(), address2.getDetail(), address2.getUser()); 42 | addressDto3 = 43 | createAddressDTO(3L, address3.getTitle(), address3.getDetail(), address3.getUser()); 44 | 45 | addressRequest = 46 | AddressRequest.builder() 47 | .title(address1.getTitle()) 48 | .detail(address1.getDetail()) 49 | .userId(address1.getUser().getId()) 50 | .build(); 51 | } 52 | 53 | protected AddressDTO createAddressDTO(Long id, String title, String detail, User user) { 54 | return AddressDTO.builder().id(id).title(title).detail(detail).userId(user.getId()).build(); 55 | } 56 | 57 | protected Address createAddress(Long id, String title, String detail, User user) { 58 | return Address.builder().id(id).title(title).detail(detail).user(user).build(); 59 | } 60 | 61 | protected User createUser(Long id, String name, String email, String username) { 62 | return User.builder().id(id).name(name).email(email).username(username).build(); 63 | } 64 | 65 | protected UserDTO createUserDto(Long id, String name, String email, String username) { 66 | return UserDTO.builder().id(id).name(name).email(email).username(username).build(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/service/impl/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | import static org.mockito.ArgumentMatchers.any; 6 | import static org.mockito.Mockito.never; 7 | import static org.mockito.Mockito.times; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.when; 10 | 11 | import com.gucardev.springboottest.dto.RestPageResponse; 12 | import com.gucardev.springboottest.dto.UserDTO; 13 | import com.gucardev.springboottest.dto.converter.UserConverter; 14 | import com.gucardev.springboottest.dto.request.UserRequest; 15 | import com.gucardev.springboottest.model.User; 16 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 17 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 18 | import com.gucardev.springboottest.remote.RemoteUserClient; 19 | import com.gucardev.springboottest.repository.UserRepository; 20 | import java.util.Arrays; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Optional; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.DisplayName; 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | import org.junit.jupiter.params.ParameterizedTest; 29 | import org.junit.jupiter.params.provider.CsvSource; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.jupiter.MockitoExtension; 32 | import org.springframework.beans.BeanUtils; 33 | import org.springframework.data.domain.Page; 34 | import org.springframework.data.domain.PageImpl; 35 | import org.springframework.data.domain.PageRequest; 36 | import org.springframework.data.domain.Pageable; 37 | import org.springframework.data.domain.Sort; 38 | import org.springframework.data.jpa.domain.Specification; 39 | 40 | @ExtendWith(MockitoExtension.class) 41 | class UserServiceTest extends UserServiceTestSupport { 42 | 43 | @Mock private UserRepository userRepository; 44 | 45 | @Mock private UserConverter userConverter; 46 | 47 | @Mock private RemoteUserClient userClient; 48 | 49 | // @InjectMocks 50 | private UserServiceImpl userService; 51 | 52 | @BeforeEach 53 | void setUp() { 54 | // MockitoAnnotations.openMocks(this); // or use class annotation 55 | userService = new UserServiceImpl(userRepository, userConverter, userClient); 56 | // userService = Mockito.spy(new UserServiceImpl(userRepository, userConverter, userClient)); 57 | super.setupTestData(); 58 | } 59 | 60 | @Test 61 | @DisplayName("getAllPageable returns all users with pagination") 62 | void getAllPageable_givenPageable_returnUsers() { 63 | Page usersPage = new PageImpl<>(Arrays.asList(user1, user2)); 64 | 65 | when(userRepository.findAll(any(Specification.class), any(Pageable.class))) 66 | .thenReturn(usersPage); 67 | when(userConverter.mapToDTO(user1)).thenReturn(userDto1); 68 | when(userConverter.mapToDTO(user2)).thenReturn(userDto2); 69 | 70 | Pageable pageable = PageRequest.of(0, 5); 71 | Page result = userService.getAllPageable("", "name", Sort.Direction.ASC, pageable); 72 | 73 | assertEquals(2, result.getTotalElements()); 74 | assertEquals(1, result.getTotalPages()); 75 | } 76 | 77 | @Test 78 | void getById_givenExistingId_returnUser() { 79 | when(userRepository.findById(existingUser.getId())).thenReturn(Optional.of(existingUser)); 80 | 81 | User result = userService.getById(existingUser.getId()); 82 | 83 | assertEquals(existingUser, result); 84 | } 85 | 86 | @Test 87 | void getById_givenNonExistentId_throwException() { 88 | Long nonExistentId = 100L; 89 | when(userRepository.findById(nonExistentId)).thenReturn(Optional.empty()); 90 | 91 | assertThrows(RuntimeException.class, () -> userService.getById(nonExistentId)); 92 | } 93 | 94 | @Test 95 | void getByIdDTO_givenExistingId_returnUserDTO() { 96 | // doReturn(existingUser).when(userService).getById(any()); // if you use spy, you can comment 97 | // code below and uncomment this. Because getById is a another method inside same test class 98 | when(userRepository.findById(existingUser.getId())).thenReturn(Optional.of(existingUser)); 99 | 100 | when(userConverter.mapToDTO(existingUser)).thenReturn(userDto1); 101 | 102 | UserDTO result = userService.getByIdDTO(existingUser.getId()); 103 | 104 | assertEquals(userDto1, result); 105 | } 106 | 107 | @Test 108 | void create_givenNewUser_returnCreatedUser() { 109 | when(userRepository.existsByUsernameIgnoreCase(userRequest.getUsername())).thenReturn(false); 110 | when(userConverter.mapToEntity(userRequest)).thenReturn(user1); 111 | when(userRepository.save(user1)).thenReturn(user1); 112 | when(userConverter.mapToDTO(user1)).thenReturn(userDto1); 113 | 114 | UserDTO result = userService.create(userRequest); 115 | 116 | assertEquals(userDto1, result); 117 | } 118 | 119 | @Test 120 | void create_givenExistingUsername_throwException() { 121 | when(userRepository.existsByUsernameIgnoreCase(userRequest.getUsername())).thenReturn(true); 122 | 123 | assertThrows(RuntimeException.class, () -> userService.create(userRequest)); 124 | } 125 | 126 | @Test 127 | void update_givenExistingUser_returnUpdatedUser() { 128 | UserRequest userRequest = 129 | UserRequest.builder() 130 | .id(user1.getId()) 131 | .username("username_will_not_update") 132 | .name("Updated User") 133 | .email("updated@test.com") 134 | .build(); 135 | User updatedUser = new User(); 136 | updatedUser.setEmail(userRequest.getEmail()); 137 | updatedUser.setName(userRequest.getName()); 138 | UserDTO updatedUserDto = new UserDTO(); 139 | BeanUtils.copyProperties(updatedUser, updatedUserDto); 140 | 141 | when(userRepository.existsById(any())).thenReturn(true); 142 | when(userRepository.findById(any())).thenReturn(Optional.of(user1)); 143 | when(userRepository.save(any())).thenReturn(updatedUser); 144 | when(userConverter.mapToDTO(updatedUser)).thenReturn(updatedUserDto); 145 | 146 | UserDTO actual = userService.update(userRequest); 147 | assertEquals(updatedUserDto, actual); 148 | } 149 | 150 | @Test 151 | void update_givenNonExistentUser_throwException() { 152 | Long nonExistentId = 100L; 153 | UserRequest nonExistentUserRequest = 154 | createUserRequest( 155 | nonExistentId, "Non-Existent User", "nonexistent@test.com", "nonExistentUser"); 156 | 157 | when(userRepository.existsById(nonExistentId)).thenReturn(false); 158 | 159 | assertThrows(RuntimeException.class, () -> userService.update(nonExistentUserRequest)); 160 | } 161 | 162 | @Test 163 | void delete_givenExistingUser_removeUser() { 164 | when(userRepository.existsById(existingUser.getId())).thenReturn(true); 165 | 166 | userService.delete(existingUser.getId()); 167 | 168 | verify(userRepository).deleteById(existingUser.getId()); 169 | } 170 | 171 | @Test 172 | void delete_givenNonExistentUser_throwException() { 173 | Long nonExistentId = 100L; 174 | when(userRepository.existsById(nonExistentId)).thenReturn(false); 175 | 176 | assertThrows(RuntimeException.class, () -> userService.delete(nonExistentId)); 177 | verify(userRepository, never()).deleteById(nonExistentId); 178 | } 179 | 180 | @Test 181 | void getUserNamesListWithLengthGreaterThan_givenLength_returnUsernames() { 182 | int length = 8; 183 | List expectedList = 184 | Arrays.asList( 185 | getUsernameLengthProjection( 186 | user1.getId(), user1.getUsername(), user1.getEmail(), user1.getUsername().length()), 187 | getUsernameLengthProjection( 188 | user2.getId(), 189 | user2.getUsername(), 190 | user2.getEmail(), 191 | user2.getUsername().length())); 192 | 193 | when(userRepository.getUserNamesListWithLengthGreaterThan(length)).thenReturn(expectedList); 194 | 195 | List result = 196 | userService.getUserNamesListWithLengthGreaterThan(length); 197 | 198 | assertEquals(expectedList, result); 199 | } 200 | 201 | @Test 202 | void getMailAndUsernames_givenNoCondition_returnMailAndUsernames() { 203 | List expectedList = 204 | Arrays.asList( 205 | getMailUsernameProjection(user1.getEmail(), user1.getUsername()), 206 | getMailUsernameProjection(user2.getEmail(), user2.getUsername())); 207 | when(userRepository.findAllMailAndUserName()).thenReturn(expectedList); 208 | 209 | List result = userService.getMailAndUsernames(); 210 | 211 | assertEquals(expectedList, result); 212 | } 213 | 214 | @Test 215 | void getDifferentUsers_givenUsernamesList_returnDifferentUsers() { 216 | RestPageResponse userDTOPage = 217 | new RestPageResponse<>(Arrays.asList(userDto1, userDto2)); 218 | when(userClient.getUsers()).thenReturn(userDTOPage); 219 | when(userRepository.findUsersNotInUsernameList(any())) 220 | .thenReturn(Collections.singletonList(user3)); 221 | 222 | when(userConverter.mapToDTO(user3)).thenReturn(userDto3); 223 | 224 | List differentUsers = userService.getDifferentUsers(); 225 | assertEquals(1, differentUsers.size()); 226 | assertEquals(userDto3, differentUsers.get(0)); 227 | verify(userConverter, times(differentUsers.size())).mapToDTO(any(User.class)); 228 | } 229 | 230 | @ParameterizedTest 231 | @CsvSource({"1,true", "2,false"}) 232 | void userExistsById_givenUserId_returnBoolean(Long id, Boolean isPresent) { 233 | Optional optionalUser = isPresent ? Optional.of(new User()) : Optional.empty(); 234 | 235 | when(userRepository.findById(id)).thenReturn(optionalUser); 236 | Boolean actual = userService.userExistsById(id); 237 | assertEquals(isPresent, actual); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/test/java/com/gucardev/springboottest/service/impl/UserServiceTestSupport.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.springboottest.service.impl; 2 | 3 | import com.gucardev.springboottest.dto.UserDTO; 4 | import com.gucardev.springboottest.dto.request.UserRequest; 5 | import com.gucardev.springboottest.model.User; 6 | import com.gucardev.springboottest.model.projection.MailUserNameProjection; 7 | import com.gucardev.springboottest.model.projection.UsernameLengthProjection; 8 | 9 | public abstract class UserServiceTestSupport { 10 | 11 | protected User user1, user2, user3, existingUser, updatedUser; 12 | protected UserDTO userDto1, userDto2, userDto3, updatedUserDto; 13 | protected UserRequest userRequest; 14 | 15 | void setupTestData() { 16 | user1 = createUser(1L, "User1", "user1@test.com", "user1"); 17 | user2 = createUser(2L, "User2", "user2@test.com", "username2"); 18 | user3 = createUser(3L, "User3", "user3@test.com", "username3"); 19 | userDto1 = createUserDto(user1.getId(), user1.getName(), user1.getEmail(), user1.getUsername()); 20 | userDto2 = createUserDto(user2.getId(), user2.getName(), user2.getEmail(), user2.getUsername()); 21 | userDto3 = createUserDto(user3.getId(), user3.getName(), user3.getEmail(), user3.getUsername()); 22 | 23 | existingUser = createUser(1L, "Existing User", "existing@test.com", "existingUser"); 24 | existingUser.setId(1L); 25 | updatedUser = createUser(1L, "Updated User", "updated@test.com", user1.getUsername()); 26 | updatedUserDto = 27 | createUserDto( 28 | updatedUser.getId(), 29 | updatedUser.getName(), 30 | updatedUser.getEmail(), 31 | updatedUser.getUsername()); 32 | 33 | userRequest = createUserRequest(1L, "Request User", "request@test.com", "requestUser"); 34 | } 35 | 36 | protected User createUser(Long id, String name, String email, String username) { 37 | return User.builder().id(id).name(name).email(email).username(username).build(); 38 | } 39 | 40 | protected UserDTO createUserDto(Long id, String name, String email, String username) { 41 | return UserDTO.builder().id(id).name(name).email(email).username(username).build(); 42 | } 43 | 44 | protected UserRequest createUserRequest(Long id, String name, String email, String username) { 45 | UserRequest userRequest = new UserRequest(); 46 | userRequest.setId(id); 47 | userRequest.setName(name); 48 | userRequest.setEmail(email); 49 | userRequest.setUsername(username); 50 | return userRequest; 51 | } 52 | 53 | protected MailUserNameProjection getMailUsernameProjection(String mail, String username) { 54 | return new MailUserNameProjection() { 55 | @Override 56 | public String getUsername() { 57 | return mail; 58 | } 59 | 60 | @Override 61 | public String getEmail() { 62 | return username; 63 | } 64 | }; 65 | } 66 | 67 | protected UsernameLengthProjection getUsernameLengthProjection( 68 | Long id, String username, String mail, Integer length) { 69 | return new UsernameLengthProjection() { 70 | @Override 71 | public String getUsername() { 72 | return mail; 73 | } 74 | 75 | @Override 76 | public Long getId() { 77 | return id; 78 | } 79 | 80 | @Override 81 | public Integer getLength() { 82 | return length; 83 | } 84 | 85 | @Override 86 | public String getEmail() { 87 | return username; 88 | } 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/resources/db/addressData.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO user_table (id) 2 | VALUES (1), 3 | (2); 4 | 5 | INSERT INTO address (id, title, detail, user_id) 6 | VALUES (1, 'Home', '123 Main St', 1), 7 | (2, 'Work', '456 Commerce Rd', 1), 8 | (3, 'Gym', '789 Fitness St', 2); 9 | --------------------------------------------------------------------------------