Skip to content

Commit

Permalink
enhancement: adding test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Hardik Singh Behl authored and Hardik Singh Behl committed Nov 20, 2023
1 parent ee81026 commit f0882a7
Show file tree
Hide file tree
Showing 8 changed files with 584 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ jobs:
java-version: '21'
distribution: 'adopt'
cache: maven
- name: Set up Docker
uses: docker/setup-buildx-action@v2
- name: Compile project
run: mvn compile
- name: Execute tests
run: mvn test
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>${testcontainer.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
38 changes: 38 additions & 0 deletions src/test/java/com/behl/cachetropolis/CacheExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.behl.cachetropolis;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.annotation.Configuration;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.utility.RandomString;

@Slf4j
@Configuration
public class CacheExtension implements BeforeAllCallback {

private static final int REDIS_PORT = 6379;
private static final String REDIS_PASSWORD = RandomString.make(10);
private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:7.2.3-alpine");

private static final GenericContainer<?> redisContainer = new GenericContainer<>(REDIS_IMAGE)
.withExposedPorts(REDIS_PORT)
.withCommand("redis-server", "--requirepass", REDIS_PASSWORD);

@Override
public void beforeAll(final ExtensionContext context) {
log.info("Creating cache container : {}", REDIS_IMAGE);
redisContainer.start();
addCacheProperties();
log.info("Successfully started cache container : {}", REDIS_IMAGE);
}

private void addCacheProperties() {
System.setProperty("spring.data.redis.host", redisContainer.getHost());
System.setProperty("spring.data.redis.port", String.valueOf(redisContainer.getMappedPort(REDIS_PORT)));
System.setProperty("spring.data.redis.password", REDIS_PASSWORD);
}

}
32 changes: 32 additions & 0 deletions src/test/java/com/behl/cachetropolis/DataSourceExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.behl.cachetropolis;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.annotation.Configuration;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
public class DataSourceExtension implements BeforeAllCallback {

private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8");
private static final MySQLContainer<?> mySQLContainer = new MySQLContainer<>(MYSQL_IMAGE);

@Override
public void beforeAll(final ExtensionContext context) {
log.info("Creating datasource container : {}", MYSQL_IMAGE);
mySQLContainer.start();
addDataSourceProperties();
log.info("Successfully started datasource container : {}", MYSQL_IMAGE);
}

private void addDataSourceProperties() {
System.setProperty("spring.datasource.url", mySQLContainer.getJdbcUrl());
System.setProperty("spring.datasource.username", mySQLContainer.getUsername());
System.setProperty("spring.datasource.password", mySQLContainer.getPassword());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.behl.cachetropolis.repository;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import com.behl.cachetropolis.DataSourceExtension;
import com.behl.cachetropolis.entity.MasterHouse;

import junit.framework.AssertionFailedError;

@DataJpaTest
@ExtendWith(DataSourceExtension.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MasterHouseRepositoryTest {

@Autowired
private MasterHouseRepository masterHouseRepository;

@Test
void shouldFetchRandomHouseRecordFromDataSource() {
// Fetch random house record from data source
Optional<MasterHouse> masterHouse = masterHouseRepository.fetchRandom();

// assert fetched record's attributes
assertThat(masterHouse).isPresent().get().satisfies(house -> {
assertThat(house.getId()).isNotNull();
assertThat(house.getName()).isNotNull();
assertThat(house.getCreatedAt()).isNotNull();
assertThat(house.getUpdatedAt()).isNotNull();
});
}

@Test
void shouldNotFetchSameHouseRecordForEachInvocation() {
// Retrieve IDs of all houses present in the data source
final List<UUID> houseIds = masterHouseRepository.findAll().stream().map(MasterHouse::getId).toList();
assertThat(houseIds).isNotEmpty().hasSizeGreaterThan(1);

// invoke method under test multiple times
final var batchSize = 100;
final List<MasterHouse> masterHouses = new ArrayList<MasterHouse>();
for (int i = 0; i < batchSize; i++) {
final var masterHouse = masterHouseRepository.fetchRandom().orElseThrow(AssertionFailedError::new);
masterHouses.add(masterHouse);
}

// Ensure randomly fetched houses contains all house-ids present in datasource
final var totalHouses = houseIds.size();
List<UUID> uniqueHouseIds = masterHouses.stream().map(MasterHouse::getId).distinct().toList();
assertThat(uniqueHouseIds).hasSize(totalHouses).containsAll(houseIds);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.behl.cachetropolis.repository;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.UUID;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import com.behl.cachetropolis.DataSourceExtension;
import com.behl.cachetropolis.entity.Wizard;

import junit.framework.AssertionFailedError;
import net.bytebuddy.utility.RandomString;

@DataJpaTest
@ExtendWith(DataSourceExtension.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class WizardRepositoryTest {

@Autowired
private WizardRepository wizardRepository;

@Autowired
private MasterHouseRepository masterHouseRepository;

@Test
void shouldReturnWizardListByHouseId() {
// fetch random house record from datasource
final var masterHouse = masterHouseRepository.fetchRandom().orElseThrow(AssertionFailedError::new);
final var houseId = masterHouse.getId();

// creare wizard record corresponding to fetched house
final var wizard = new Wizard();
final var firstName = RandomString.make();
wizard.setFirstName(firstName);
wizard.setGender("Male");
wizard.setHouseId(houseId);
wizardRepository.save(wizard);

// invoke method under test
List<Wizard> wizards = wizardRepository.findByHouseId(houseId);

// assert datasource response contains saved wizard record
assertThat(wizards).isNotEmpty().map(Wizard::getFirstName).contains(firstName);
}

@Test
void shouldReturnEmptyWizardListForInvalidHouseId() {
// generate random house-id
final var invalidHouseId = UUID.randomUUID();

// invoke method under test
List<Wizard> wizards = wizardRepository.findByHouseId(invalidHouseId);

// assert datasource response is empty list
assertThat(wizards).isEmpty();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.behl.cachetropolis.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.List;
import java.util.UUID;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cache.CacheManager;

import com.behl.cachetropolis.CacheExtension;
import com.behl.cachetropolis.DataSourceExtension;
import com.behl.cachetropolis.dto.HouseDto;
import com.behl.cachetropolis.dto.WizardDto;
import com.behl.cachetropolis.entity.Wizard;
import com.behl.cachetropolis.exception.InvalidHouseIdException;
import com.behl.cachetropolis.repository.MasterHouseRepository;
import com.behl.cachetropolis.repository.WizardRepository;

import junit.framework.AssertionFailedError;
import net.bytebuddy.utility.RandomString;

@SpringBootTest
@ExtendWith({ DataSourceExtension.class, CacheExtension.class })
class MasterHouseServiceTest {

@Autowired
private MasterHouseService masterHouseService;

@SpyBean
private WizardRepository wizardRepository;

@SpyBean
private MasterHouseRepository masterHouseRepository;

@Autowired
private CacheManager listCacheManager;

@Test
void shouldRetrieveWizardsAgainstValidHouseId() {
// fetch random house record from datasource
final var house = masterHouseRepository.fetchRandom().orElseThrow(AssertionFailedError::new);

// create wizard record
final var firstName = RandomString.make();
final var lastName = RandomString.make();
final var wizard = new Wizard();
wizard.setFirstName(firstName);
wizard.setLastName(lastName);
wizard.setGender("Male");
wizard.setHouseId(house.getId());
wizardRepository.save(wizard);

// invoke method under test
final List<WizardDto> retrievedWizards = masterHouseService.retrieveWizardsByHouseId(house.getId());

// assert presence of saved wizard in retrieved records and verify datasource interaction
assertThat(retrievedWizards).isNotNull().isNotEmpty();
assertThat(retrievedWizards).map(WizardDto::getFirstName).contains(firstName);
assertThat(retrievedWizards).map(WizardDto::getLastName).contains(lastName);
verify(wizardRepository, times(1)).findByHouseId(house.getId());
}

@Test
void shouldRetrieveWizardRecordsByHouseIdFromCacheAfterInitialDatabaseRetrieval() {
// fetch random house record from datasource
final var house = masterHouseRepository.fetchRandom().orElseThrow(AssertionFailedError::new);
final var houseId = house.getId();

// create wizard record
final var firstName = RandomString.make();
final var lastName = RandomString.make();
final var wizard = new Wizard();
wizard.setFirstName(firstName);
wizard.setLastName(lastName);
wizard.setGender("Male");
wizard.setHouseId(houseId);
wizardRepository.save(wizard);

// assert cache does not contain wizard records corresponding to house-id
var cachedWizardRecords = listCacheManager.getCache("wizards").get(houseId);
assertThat(cachedWizardRecords).isNull();

// invoke method under test and assert that initial retrieval is done from datasource
masterHouseService.retrieveWizardsByHouseId(houseId);
verify(wizardRepository, times(1)).findByHouseId(houseId);
Mockito.clearInvocations(wizardRepository);

// invoke method under test multiple times to verify subsequent reads are made from cache and datasource is not queried
final var queryTimes = 100;
for (int i = 1; i < queryTimes; i++) {
masterHouseService.retrieveWizardsByHouseId(houseId);
}
verify(wizardRepository, times(0)).findByHouseId(houseId);
}

@Test
void shouldThrowExceptionForInvalidHouseId() {
// prepare invalid house-id
final var houseId = UUID.randomUUID();

// invoke method under test and verify datasource interaction
assertThrows(InvalidHouseIdException.class, () -> masterHouseService.retrieveWizardsByHouseId(houseId));
verify(wizardRepository, times(1)).findByHouseId(houseId);
}

@Test
void shouldReturnNonEmptyHouseListPostFlywayMigrationExecution() {
// Retrieve the list of house records saved in the datasource
final var retrievedHouses = masterHouseService.retrieve();

// Verify that the list of countries is not empty and flyway migration script is executed
assertThat(retrievedHouses).isNotNull().isNotEmpty().hasSizeGreaterThanOrEqualTo(4);
assertThat(retrievedHouses).map(HouseDto::getId).doesNotContainNull();
assertThat(retrievedHouses).map(HouseDto::getName).doesNotContainNull();
}

@Test
void shouldFetchRandomHouseRecordFromDataSource() {
// invoke method under test
final var retrievedHouse = masterHouseService.retrieveRandom();

// assert response attributes
assertThat(retrievedHouse).isNotNull().isInstanceOf(HouseDto.class)
.satisfies(house -> {
assertThat(house.getId()).isNotNull();
assertThat(house.getName()).isNotNull();
});
}

}
Loading

0 comments on commit f0882a7

Please sign in to comment.