Retrospective about what happened when calling API


Environment and Prerequisite

  • API Call Experience


What I experienced when calling API

There was a code worked well. It was a code that filtering what we wanted after get the result of API. However one day it did not work in one of our services. After take a looking it, we found that there were values what we wanted on web but those values were not exist after filtering. I looked into it long time but I thought there was no problem. However there was a problem with the code. It wasn’t the code’s fault, it was developer’s fault!

With help of my colleague, we inspired by API response result item size before filtering. Its size was a multiple of 5 or 10. We found that there was a limit of API response item size. That was why we could not get the values what we wanted. In my code, we received the response items and filtered it but there was a max limit on item size. So we could not get what we wanted. Problem was max item size in API call.

Let’s suppose expected value is the 101st but API result contain only until 100th value so we cannot get the expected 101st value.

At last we add conditions to API call and add pagination code to API call to solve above issue.

I learned three things.

  1. To minimize burden and mistake of API caller, add conditions to API call rather than get all response items.
  2. Be careful when use API call because there can be a limit of response item size.
  3. Solving problems together can make solving issue faster.

It was a simple thing but I took some time to solve it. So I wrote a post about it.

Be careful next time!


Reference

rsync에서 22번 포트가 아닌 다른 포트를 사용해보자


환경

  • Linux
  • Bash shell(/bin/bash)
  • rsync


rsync

방법

  • -e 옵션을 사용
  • 아래 예시에서는 모두 2222번 포트를 사용하도록 작성함
-e 'ssh -p 2222'


IP주소 혹은 도메인을 입력하는 경우

  • 형태
rsync -e 'ssh -p 2222' [target file or directory] [user]@[IP address or domain]:[destination]
  • 예시
rsync -e 'ssh -p 2222' test.txt twpower@192.168.1.2:~/test.txt


~/.ssh/config 파일을 사용하는 경우

  • 형태
Host test_host
    ...
    Port 2222
    ...
rsync [target file or directory] [host name]:[destination]
  • 예시
rsync test.txt test_host:~/test.txt


참고자료

Use different port not 22 in rsync


Environment and Prerequisite

  • Linux
  • Bash shell(/bin/bash)
  • rsync


rsync

Method

  • Use -e option
  • All of the examples below are written to use port 2222
-e 'ssh -p 2222'


In case when use IP address or domain

  • Format
rsync -e 'ssh -p 2222' [target file or directory] [user]@[IP address or domain]:[destination]
  • Example
rsync -e 'ssh -p 2222' test.txt twpower@192.168.1.2:~/test.txt


In case when use ~/.ssh/config file

  • Format
Host test_host
    ...
    Port 2222
    ...
rsync [target file or directory] [host name]:[destination]
  • Example
rsync test.txt test_host:~/test.txt


Reference

Mockito를 사용해 Spring Service를 테스트 해보자


환경

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


Mockito를 통한 단위 테스트

Mockito

  • Mockito: Java에서 모킹(Mocking)을 지원해주는 테스트 프레임워크
  • @Mock: 테스트에 필요한 객체를 모킹(Mocking)해주는 어노테이션
  • @InjectMocks: 테스트에 필요한 객체를 모킹(Mocking)해주는것과 함께 @Mock으로 모킹된 객체들 중에서 필요한 객체들을 주입해주는 어노테이션
  • when(method_name).thenReturn(return_value);: 모킹(Mocking)된 객체의 특정 메소드(method_name) 호출시 return_value를 반환하도록 설정 가능한 메소드


예제

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testImplementation('org.mockito:mockito-core')
	testImplementation('org.mockito:mockito-junit-jupiter')
	testImplementation('org.hamcrest:hamcrest')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
    Boolean existsByEmail(String email);
}

Service

@Slf4j
@Service
public class UserService {

    @Autowired
    UserRepository userRepository;

    public UserEntity create(final UserEntity userEntity){
        if (userEntity == null || userEntity.getEmail() == null) {
            throw new RuntimeException("Invalid Arguments");
        }

        final String email = userEntity.getEmail();
        if (userRepository.existsByEmail(email)) {
            log.warn("Email already exists {}", email);
            throw new RuntimeException("Email already exists");
        }

        return userRepository.save(userEntity);
    }

}

Test

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void createTest() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(null);
        });

        UserEntity userEntityForNullEmail = UserEntity.builder().build();
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForNullEmail);
        });

        UserEntity userEntityForExistUser = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        when(userRepository.existsByEmail(any())).thenReturn(true);
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForExistUser);
        });

        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        reset(userRepository);
        when(userRepository.save(any())).thenReturn(userEntity);
        UserEntity returnedUserEntity = userService.create(userEntity);
        Assertions.assertEquals("test@test.com", returnedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", returnedUserEntity.getUsername());
        Assertions.assertEquals("testPassword", returnedUserEntity.getPassword());
    }
}


참고자료

Test Spring Service Using Mockito


Environment and Prerequisite

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


Mockito

Mockito

  • Mockito: Java test framework which supports mocking
  • @Mock: Object mocking annotation for test
  • @InjectMocks: Object mocking annotation with inject all needed objects with @Mock annotation for test
  • when(method_name).thenReturn(return_value);: When call mocking object’s specific method(method_name), this method make it to return return_value


Example

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testImplementation('org.mockito:mockito-core')
	testImplementation('org.mockito:mockito-junit-jupiter')
	testImplementation('org.hamcrest:hamcrest')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
    Boolean existsByEmail(String email);
}

Service

@Slf4j
@Service
public class UserService {

    @Autowired
    UserRepository userRepository;

    public UserEntity create(final UserEntity userEntity){
        if (userEntity == null || userEntity.getEmail() == null) {
            throw new RuntimeException("Invalid Arguments");
        }

        final String email = userEntity.getEmail();
        if (userRepository.existsByEmail(email)) {
            log.warn("Email already exists {}", email);
            throw new RuntimeException("Email already exists");
        }

        return userRepository.save(userEntity);
    }

}

Test

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void createTest() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(null);
        });

        UserEntity userEntityForNullEmail = UserEntity.builder().build();
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForNullEmail);
        });

        UserEntity userEntityForExistUser = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        when(userRepository.existsByEmail(any())).thenReturn(true);
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForExistUser);
        });

        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        reset(userRepository);
        when(userRepository.save(any())).thenReturn(userEntity);
        UserEntity returnedUserEntity = userService.create(userEntity);
        Assertions.assertEquals("test@test.com", returnedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", returnedUserEntity.getUsername());
        Assertions.assertEquals("testPassword", returnedUserEntity.getPassword());
    }
}


Reference