@CircuitBreaker
@Transactional
@Cacheable
@ConditionalOn...
ApplicationContextRunner
@PreAuthorize
, AOP[Integration Testing] lets you test the correct wiring of your Spring IoC container contexts
Każdy start kontekstu Springa to już integracja
Każdy start kontekstu Springa to już integracja
testImplementation 'org.springframework.boot:spring-boot-starter-test'
package io.github.mat3e.downloads.limiting;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.reporting.ReportingFacade;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.time.Clock;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class LimitingFacadeTest {
private final Clock clock = mock(Clock.class);
private final AccountRepository accountRepository = mock(AccountRepository.class);
private final AccountSettingRepository accountSettingRepository = mock(AccountSettingRepository.class);
private final ReportingFacade reporting = mock(ReportingFacade.class);
private LimitingFacade systemUnderTest;
@BeforeEach
void setUp() {
systemUnderTest = new LimitingFacade(clock, accountRepository, accountSettingRepository, reporting);
}
@Test
void newAccount_overrideLimit_createsAccount() {
// given
var accountId = AccountId.valueOf("1");
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.empty());
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
var accountSettingCaptor = ArgumentCaptor.forClass(AccountSetting.class);
verify(accountSettingRepository).save(accountSettingCaptor.capture());
assertThat(accountSettingCaptor.getValue().id()).isEqualTo(accountId);
assertThat(accountSettingCaptor.getValue().limit()).isEqualTo(1);
}
@Test
void existingAccount_overrideLimit_updatesAccount() {
// given
var accountId = AccountId.valueOf("1");
var existingAccount = mock(AccountSetting.class);
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.of(existingAccount));
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
verify(existingAccount).overrideLimit(1);
verify(accountSettingRepository).save(existingAccount);
}
}
package io.github.mat3e.downloads.limiting;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.reporting.ReportingFacade;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.time.Clock;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class LimitingFacadeTest {
private final Clock clock = mock(Clock.class);
private final AccountRepository accountRepository = mock(AccountRepository.class);
private final AccountSettingRepository accountSettingRepository = mock(AccountSettingRepository.class);
private final ReportingFacade reporting = mock(ReportingFacade.class);
private final LimitingFacade systemUnderTest =
new LimitingFacade(clock, accountRepository, accountSettingRepository, reporting);
@Test
void newAccount_overrideLimit_createsAccount() {
// given
var accountId = AccountId.valueOf("1");
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.empty());
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
var accountSettingCaptor = ArgumentCaptor.forClass(AccountSetting.class);
verify(accountSettingRepository).save(accountSettingCaptor.capture());
assertThat(accountSettingCaptor.getValue().id()).isEqualTo(accountId);
assertThat(accountSettingCaptor.getValue().limit()).isEqualTo(1);
}
@Test
void existingAccount_overrideLimit_updatesAccount() {
// given
var accountId = AccountId.valueOf("1");
var existingAccount = mock(AccountSetting.class);
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.of(existingAccount));
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
verify(existingAccount).overrideLimit(1);
verify(accountSettingRepository).save(existingAccount);
}
}
package io.github.mat3e.downloads.limiting;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.reporting.ReportingFacade;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.Clock;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class LimitingFacadeTest {
@Mock
private Clock clock;
@Mock
private AccountRepository accountRepository;
@Mock
private AccountSettingRepository accountSettingRepository;
@Mock
private ReportingFacade reporting;
@InjectMocks
private LimitingFacade systemUnderTest;
@Captor
private ArgumentCaptor<AccountSetting> accountSettingCaptor;
@Test
void newAccount_overrideLimit_createsAccount() {
// given
var accountId = AccountId.valueOf("1");
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.empty());
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
verify(accountSettingRepository).save(accountSettingCaptor.capture());
var account = accountSettingCaptor.getValue();
assertThat(account.id()).isEqualTo(accountId);
assertThat(account.limit()).isEqualTo(1);
}
@Test
void existingAccount_overrideLimit_updatesAccount() {
// given
var accountId = AccountId.valueOf("1");
var existingAccount = new AccountSetting(accountId.getId(), 2, 1);
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.of(existingAccount));
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
assertThat(existingAccount.limit()).isEqualTo(1);
verify(accountSettingRepository).save(existingAccount);
}
}
package io.github.mat3e.downloads.limiting;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.reporting.ReportingFacade;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.Clock;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class LimitingFacadeTest {
@Mock
private Clock clock;
@Mock
private AccountRepository accountRepository;
@Mock
private AccountSettingRepository accountSettingRepository;
@Mock
private ReportingFacade reporting;
@InjectMocks
private LimitingFacade systemUnderTest;
@Captor
private ArgumentCaptor<AccountSetting> accountSettingCaptor;
@Test
void newAccount_overrideLimit_createsAccount() {
// given
var accountId = AccountId.valueOf("1");
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.empty());
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
verify(accountSettingRepository).save(accountSettingCaptor.capture());
var account = accountSettingCaptor.getValue();
assertThat(account.id()).isEqualTo(accountId);
assertThat(account.limit()).isEqualTo(1);
}
@Test
void existingAccount_overrideLimit_updatesAccount() {
// given
var accountId = AccountId.valueOf("1");
var existingAccount = new AccountSetting(accountId.getId(), 2, 1);
when(accountSettingRepository.findById(accountId)).thenReturn(Optional.of(existingAccount));
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
// then
assertThat(existingAccount.limit()).isEqualTo(1);
verify(accountSettingRepository).save(existingAccount);
}
}
package io.github.mat3e.downloads.limiting;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.reporting.ReportingFacade;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.Clock;
import java.util.Optional;
import static org.assertj.core.api.BDDAssertions.and;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
@ExtendWith(MockitoExtension.class)
class LimitingFacadeTest {
@Mock
private Clock clock;
@Mock
private AccountRepository accountRepository;
@Mock
private AccountSettingRepository accountSettingRepository;
@Mock
private ReportingFacade reporting;
@InjectMocks
private LimitingFacade systemUnderTest;
@Captor
private ArgumentCaptor<AccountSetting> accountSettingCaptor;
@Test
void newAccount_overrideLimit_createsAccount() {
var accountId = AccountId.valueOf("1");
given(accountSettingRepository.findById(accountId)).willReturn(Optional.empty());
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
then(accountSettingRepository).should().save(accountSettingCaptor.capture());
var account = accountSettingCaptor.getValue();
and.then(account.id()).isEqualTo(accountId);
and.then(account.limit()).isEqualTo(1);
}
@Test
void existingAccount_overrideLimit_updatesAccount() {
var accountId = AccountId.valueOf("1");
var existingAccount = new AccountSetting(accountId.getId(), 2, 1);
given(accountSettingRepository.findById(accountId)).willReturn(Optional.of(existingAccount));
// when
systemUnderTest.overrideAccountLimit(accountId, 1);
then(accountSettingRepository).should().save(existingAccount);
and.then(existingAccount.limit()).isEqualTo(1);
}
}
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.JsonContent;
then(asJson(item)).isStrictlyEqualToJson("""
{
"itemId": 123,
"itemType": "movie",
"item": {
"name": "Test",
"slug": "test-slug"
}
}""");
private JsonContent<Object> asJson(CollectionItem toParse) throws JsonProcessingException {
String jsonResult = new ObjectMapper().writeValueAsString(toParse);
return new BasicJsonTester(getClass()).from(jsonResult);
}
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
@Test
@ExtendWith(OutputCaptureExtension.class)
void should_warn_about_null_country(CapturedOutput capturedOutput) {
// when
SubscriptionCountry.from((Country) null);
then(capturedOutput.getOut()).contains("nullable Country");
}
package io.github.mat3e.downloads.limiting.rest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.github.mat3e.downloads.limiting.LimitingFacade;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.limiting.api.Asset;
import io.github.mat3e.downloads.limiting.api.AssetDeserialization;
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.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.Collection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@SpringBootTest
@ExtendWith(SpringExtension.class)
class LimitingIntTest {
private static final String ACCOUNT_ID = "1";
@Autowired
private MockMvc mockMvc;
@Test
void downloadStarted_storesAssetsTillLimit() throws Exception {
givenAccountLimit(2);
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"123\",",
" \"countryCode\": \"US\"",
"}");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"456\",",
" \"countryCode\": \"US\"",
"}");
// when
httpPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}"
).andExpect(status().isUnprocessableEntity());
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("123").inCountry("US"),
Asset.withId("456").inCountry("US"));
// when
httpSuccessfulDeleteAsset("123", "US");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}");
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("456").inCountry("US"),
Asset.withId("789").inCountry("US"));
}
@Test
void illegalParams_returnsClientError() throws Exception {
// given
var validBody = "{ \"id\": \"123\", \"countryCode\": \"US\" }";
// expect 404 - no account created, no assets
httpGetAssets("lookMaNotExistingId").andExpect(status().isNotFound());
httpPostAsset(validBody).andExpect(status().isNotFound());
httpDeleteAsset("123", "US").andExpect(status().isNotFound());
// expect 400
httpPostAssetForAccount(" ", validBody).andExpect(status().isBadRequest());
httpPostAsset("{ \"countryCode\": \"US\" }").andExpect(status().isBadRequest());
httpPostAsset("{ \"id\": \"123\" }").andExpect(status().isBadRequest());
httpDeleteAsset(" ", "OK").andExpect(status().isBadRequest());
httpDeleteAsset("123", " ").andExpect(status().isBadRequest());
}
private void httpSuccessfulDeleteAsset(String assetId, String countryCode) {
try {
httpDeleteAsset(assetId, countryCode).andExpect(status().isNoContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpDeleteAsset(String assetId, String countryCode) throws Exception {
return mockMvc.perform(delete("/api/accounts/{id}/assets/{assetId}", ACCOUNT_ID, assetId)
.queryParam("countryCode", countryCode));
}
private Collection<Asset> httpSuccessfulGetAssets() {
try {
String jsonResponse = httpGetAssets(ACCOUNT_ID)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JavaType returnType =
TypeFactory.defaultInstance().constructCollectionType(Collection.class, AssetDeserialization.class);
return new ObjectMapper().<Collection<AssetDeserialization>>readValue(jsonResponse, returnType).stream()
.map(AssetDeserialization::toApi)
.collect(toUnmodifiableList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpGetAssets(String accountId) throws Exception {
return mockMvc.perform(get("/api/accounts/{id}/assets", accountId).contentType(APPLICATION_JSON));
}
private void httpSuccessfulPostAsset(String... jsonLines) {
try {
httpPostAsset(jsonLines).andExpect(status().isCreated());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpPostAsset(String... jsonLines) throws Exception {
return httpPostAssetForAccount(ACCOUNT_ID, jsonLines);
}
private ResultActions httpPostAssetForAccount(String accountId, String... jsonLines) throws Exception {
return mockMvc.perform(post("/api/accounts/{id}/assets", accountId)
.contentType(APPLICATION_JSON)
.content(String.join("\n", jsonLines)));
}
void givenAccountLimit(int limit) {
facade.overrideAccountLimit(AccountId.valueOf(ACCOUNT_ID), limit);
}
@Autowired
private LimitingFacade facade;
}
package io.github.mat3e.downloads.limiting.rest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.github.mat3e.downloads.limiting.LimitingFacade;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.limiting.api.Asset;
import io.github.mat3e.downloads.limiting.api.AssetDeserialization;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.Collection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@SpringBootTest
class LimitingIntTest {
private static final String ACCOUNT_ID = "1";
@Autowired
private MockMvc mockMvc;
@Test
void downloadStarted_storesAssetsTillLimit() throws Exception {
givenAccountLimit(2);
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"123\",",
" \"countryCode\": \"US\"",
"}");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"456\",",
" \"countryCode\": \"US\"",
"}");
// when
httpPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}"
).andExpect(status().isUnprocessableEntity());
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("123").inCountry("US"),
Asset.withId("456").inCountry("US"));
// when
httpSuccessfulDeleteAsset("123", "US");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}");
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("456").inCountry("US"),
Asset.withId("789").inCountry("US"));
}
@Test
void illegalParams_returnsClientError() throws Exception {
// given
var validBody = "{ \"id\": \"123\", \"countryCode\": \"US\" }";
// expect 404 - no account created, no assets
httpGetAssets("lookMaNotExistingId").andExpect(status().isNotFound());
httpPostAsset(validBody).andExpect(status().isNotFound());
httpDeleteAsset("123", "US").andExpect(status().isNotFound());
// expect 400
httpPostAssetForAccount(" ", validBody).andExpect(status().isBadRequest());
httpPostAsset("{ \"countryCode\": \"US\" }").andExpect(status().isBadRequest());
httpPostAsset("{ \"id\": \"123\" }").andExpect(status().isBadRequest());
httpDeleteAsset(" ", "OK").andExpect(status().isBadRequest());
httpDeleteAsset("123", " ").andExpect(status().isBadRequest());
}
private void httpSuccessfulDeleteAsset(String assetId, String countryCode) {
try {
httpDeleteAsset(assetId, countryCode).andExpect(status().isNoContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpDeleteAsset(String assetId, String countryCode) throws Exception {
return mockMvc.perform(delete("/api/accounts/{id}/assets/{assetId}", ACCOUNT_ID, assetId)
.queryParam("countryCode", countryCode));
}
private Collection<Asset> httpSuccessfulGetAssets() {
try {
String jsonResponse = httpGetAssets(ACCOUNT_ID)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JavaType returnType =
TypeFactory.defaultInstance().constructCollectionType(Collection.class, AssetDeserialization.class);
return new ObjectMapper().<Collection<AssetDeserialization>>readValue(jsonResponse, returnType).stream()
.map(AssetDeserialization::toApi)
.collect(toUnmodifiableList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpGetAssets(String accountId) throws Exception {
return mockMvc.perform(get("/api/accounts/{id}/assets", accountId).contentType(APPLICATION_JSON));
}
private void httpSuccessfulPostAsset(String... jsonLines) {
try {
httpPostAsset(jsonLines).andExpect(status().isCreated());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpPostAsset(String... jsonLines) throws Exception {
return httpPostAssetForAccount(ACCOUNT_ID, jsonLines);
}
private ResultActions httpPostAssetForAccount(String accountId, String... jsonLines) throws Exception {
return mockMvc.perform(post("/api/accounts/{id}/assets", accountId)
.contentType(APPLICATION_JSON)
.content(String.join("\n", jsonLines)));
}
void givenAccountLimit(int limit) {
facade.overrideAccountLimit(AccountId.valueOf(ACCOUNT_ID), limit);
}
@Autowired
private LimitingFacade facade;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
public @interface SpringBootTest {
/* ... */
}
/**
* Annotation for a JPA test that focuses <strong>only</strong> on JPA components.
* <p>
* Using this annotation will disable full auto-configuration and instead apply only
* configuration relevant to JPA tests.
* <p>
* By default, tests annotated with {@code @DataJpaTest} are transactional and roll back
* at the end of each test. They also use an embedded in-memory database (replacing any
* explicit or usually auto-configured DataSource).
* ...
* If you are looking to load your full application configuration, but use an embedded
* database, you should consider {@link SpringBootTest @SpringBootTest} combined with
* {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase} rather than this
* annotation.
* ...
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
/* ... */
}
package io.github.mat3e.downloads.limiting.rest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.github.mat3e.downloads.limiting.LimitingFacade;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.limiting.api.Asset;
import io.github.mat3e.downloads.limiting.api.AssetDeserialization;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.Collection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@SpringBootTest
class LimitingIntTest {
private static final String ACCOUNT_ID = "1";
@Autowired
private MockMvc mockMvc;
@Test
void downloadStarted_storesAssetsTillLimit() throws Exception {
givenAccountLimit(2);
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"123\",",
" \"countryCode\": \"US\"",
"}");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"456\",",
" \"countryCode\": \"US\"",
"}");
// when
httpPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}"
).andExpect(status().isUnprocessableEntity());
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("123").inCountry("US"),
Asset.withId("456").inCountry("US"));
// when
httpSuccessfulDeleteAsset("123", "US");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}");
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("456").inCountry("US"),
Asset.withId("789").inCountry("US"));
}
@Test
void illegalParams_returnsClientError() throws Exception {
// given
var validBody = "{ \"id\": \"123\", \"countryCode\": \"US\" }";
// expect 404 - no account created, no assets
httpGetAssets("lookMaNotExistingId").andExpect(status().isNotFound());
httpPostAsset(validBody).andExpect(status().isNotFound());
httpDeleteAsset("123", "US").andExpect(status().isNotFound());
// expect 400
httpPostAssetForAccount(" ", validBody).andExpect(status().isBadRequest());
httpPostAsset("{ \"countryCode\": \"US\" }").andExpect(status().isBadRequest());
httpPostAsset("{ \"id\": \"123\" }").andExpect(status().isBadRequest());
httpDeleteAsset(" ", "OK").andExpect(status().isBadRequest());
httpDeleteAsset("123", " ").andExpect(status().isBadRequest());
}
private void httpSuccessfulDeleteAsset(String assetId, String countryCode) {
try {
httpDeleteAsset(assetId, countryCode).andExpect(status().isNoContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpDeleteAsset(String assetId, String countryCode) throws Exception {
return mockMvc.perform(delete("/api/accounts/{id}/assets/{assetId}", ACCOUNT_ID, assetId)
.queryParam("countryCode", countryCode));
}
private Collection<Asset> httpSuccessfulGetAssets() {
try {
String jsonResponse = httpGetAssets(ACCOUNT_ID)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JavaType returnType =
TypeFactory.defaultInstance().constructCollectionType(Collection.class, AssetDeserialization.class);
return new ObjectMapper().<Collection<AssetDeserialization>>readValue(jsonResponse, returnType).stream()
.map(AssetDeserialization::toApi)
.collect(toUnmodifiableList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpGetAssets(String accountId) throws Exception {
return mockMvc.perform(get("/api/accounts/{id}/assets", accountId).contentType(APPLICATION_JSON));
}
private void httpSuccessfulPostAsset(String... jsonLines) {
try {
httpPostAsset(jsonLines).andExpect(status().isCreated());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpPostAsset(String... jsonLines) throws Exception {
return httpPostAssetForAccount(ACCOUNT_ID, jsonLines);
}
private ResultActions httpPostAssetForAccount(String accountId, String... jsonLines) throws Exception {
return mockMvc.perform(post("/api/accounts/{id}/assets", accountId)
.contentType(APPLICATION_JSON)
.content(String.join("\n", jsonLines)));
}
void givenAccountLimit(int limit) {
facade.overrideAccountLimit(AccountId.valueOf(ACCOUNT_ID), limit);
}
@Autowired
private LimitingFacade facade;
}
package io.github.mat3e.downloads.limiting.rest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.github.mat3e.downloads.limiting.LimitingFacade;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.limiting.api.Asset;
import io.github.mat3e.downloads.limiting.api.AssetDeserialization;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Transactional
@AutoConfigureMockMvc
@SpringBootTest
class LimitingIntTest {
private static final String ACCOUNT_ID = "1";
@Autowired
private MockMvc mockMvc;
@Test
void downloadStarted_storesAssetsTillLimit() throws Exception {
givenAccountLimit(2);
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"123\",",
" \"countryCode\": \"US\"",
"}");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"456\",",
" \"countryCode\": \"US\"",
"}");
// when
httpPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}"
).andExpect(status().isUnprocessableEntity());
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("123").inCountry("US"),
Asset.withId("456").inCountry("US"));
// when
httpSuccessfulDeleteAsset("123", "US");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}");
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("456").inCountry("US"),
Asset.withId("789").inCountry("US"));
}
@Test
void illegalParams_returnsClientError() throws Exception {
// given
var validBody = "{ \"id\": \"123\", \"countryCode\": \"US\" }";
// expect 404 - no account created, no assets
httpGetAssets("lookMaNotExistingId").andExpect(status().isNotFound());
httpPostAsset(validBody).andExpect(status().isNotFound());
httpDeleteAsset("123", "US").andExpect(status().isNotFound());
// expect 400
httpPostAssetForAccount(" ", validBody).andExpect(status().isBadRequest());
httpPostAsset("{ \"countryCode\": \"US\" }").andExpect(status().isBadRequest());
httpPostAsset("{ \"id\": \"123\" }").andExpect(status().isBadRequest());
httpDeleteAsset(" ", "OK").andExpect(status().isBadRequest());
httpDeleteAsset("123", " ").andExpect(status().isBadRequest());
}
private void httpSuccessfulDeleteAsset(String assetId, String countryCode) {
try {
httpDeleteAsset(assetId, countryCode).andExpect(status().isNoContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpDeleteAsset(String assetId, String countryCode) throws Exception {
return mockMvc.perform(delete("/api/accounts/{id}/assets/{assetId}", ACCOUNT_ID, assetId)
.queryParam("countryCode", countryCode));
}
private Collection<Asset> httpSuccessfulGetAssets() {
try {
String jsonResponse = httpGetAssets(ACCOUNT_ID)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JavaType returnType =
TypeFactory.defaultInstance().constructCollectionType(Collection.class, AssetDeserialization.class);
return new ObjectMapper().<Collection<AssetDeserialization>>readValue(jsonResponse, returnType).stream()
.map(AssetDeserialization::toApi)
.collect(toUnmodifiableList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpGetAssets(String accountId) throws Exception {
return mockMvc.perform(get("/api/accounts/{id}/assets", accountId).contentType(APPLICATION_JSON));
}
private void httpSuccessfulPostAsset(String... jsonLines) {
try {
httpPostAsset(jsonLines).andExpect(status().isCreated());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpPostAsset(String... jsonLines) throws Exception {
return httpPostAssetForAccount(ACCOUNT_ID, jsonLines);
}
private ResultActions httpPostAssetForAccount(String accountId, String... jsonLines) throws Exception {
return mockMvc.perform(post("/api/accounts/{id}/assets", accountId)
.contentType(APPLICATION_JSON)
.content(String.join("\n", jsonLines)));
}
void givenAccountLimit(int limit) {
facade.overrideAccountLimit(AccountId.valueOf(ACCOUNT_ID), limit);
}
@Autowired
private LimitingFacade facade;
}
⚠️ @Transactional
+ JPA ⚠️
Spring pitfalls: transactional tests considered harmful
package io.github.mat3e.downloads.limiting.rest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.github.mat3e.downloads.limiting.LimitingFacade;
import io.github.mat3e.downloads.limiting.api.AccountId;
import io.github.mat3e.downloads.limiting.api.Asset;
import io.github.mat3e.downloads.limiting.api.AssetDeserialization;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Transactional
@AutoConfigureMockMvc
@SpringBootTest
class LimitingIntTest {
private static final String ACCOUNT_ID = "1";
@Autowired
private MockMvc mockMvc;
@Test
void downloadStarted_storesAssetsTillLimit() throws Exception {
givenAccountLimit(2);
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"123\",",
" \"countryCode\": \"US\"",
"}");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"456\",",
" \"countryCode\": \"US\"",
"}");
// when
httpPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}"
).andExpect(status().isUnprocessableEntity());
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("123").inCountry("US"),
Asset.withId("456").inCountry("US"));
// when
httpSuccessfulDeleteAsset("123", "US");
// and
httpSuccessfulPostAsset(
"{",
" \"id\": \"789\",",
" \"countryCode\": \"US\"",
"}");
then(httpSuccessfulGetAssets()).containsExactly(
Asset.withId("456").inCountry("US"),
Asset.withId("789").inCountry("US"));
}
@Test
void illegalParams_returnsClientError() throws Exception {
// given
var validBody = "{ \"id\": \"123\", \"countryCode\": \"US\" }";
// expect 404 - no account created, no assets
httpGetAssets("lookMaNotExistingId").andExpect(status().isNotFound());
httpPostAsset(validBody).andExpect(status().isNotFound());
httpDeleteAsset("123", "US").andExpect(status().isNotFound());
// expect 400
httpPostAssetForAccount(" ", validBody).andExpect(status().isBadRequest());
httpPostAsset("{ \"countryCode\": \"US\" }").andExpect(status().isBadRequest());
httpPostAsset("{ \"id\": \"123\" }").andExpect(status().isBadRequest());
httpDeleteAsset(" ", "OK").andExpect(status().isBadRequest());
httpDeleteAsset("123", " ").andExpect(status().isBadRequest());
}
private void httpSuccessfulDeleteAsset(String assetId, String countryCode) {
try {
httpDeleteAsset(assetId, countryCode).andExpect(status().isNoContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpDeleteAsset(String assetId, String countryCode) throws Exception {
return mockMvc.perform(delete("/api/accounts/{id}/assets/{assetId}", ACCOUNT_ID, assetId)
.queryParam("countryCode", countryCode));
}
private Collection<Asset> httpSuccessfulGetAssets() {
try {
String jsonResponse = httpGetAssets(ACCOUNT_ID)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JavaType returnType =
TypeFactory.defaultInstance().constructCollectionType(Collection.class, AssetDeserialization.class);
return new ObjectMapper().<Collection<AssetDeserialization>>readValue(jsonResponse, returnType).stream()
.map(AssetDeserialization::toApi)
.collect(toUnmodifiableList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpGetAssets(String accountId) throws Exception {
return mockMvc.perform(get("/api/accounts/{id}/assets", accountId).contentType(APPLICATION_JSON));
}
private void httpSuccessfulPostAsset(String... jsonLines) {
try {
httpPostAsset(jsonLines).andExpect(status().isCreated());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ResultActions httpPostAsset(String... jsonLines) throws Exception {
return httpPostAssetForAccount(ACCOUNT_ID, jsonLines);
}
private ResultActions httpPostAssetForAccount(String accountId, String... jsonLines) throws Exception {
return mockMvc.perform(post("/api/accounts/{id}/assets", accountId)
.contentType(APPLICATION_JSON)
.content(String.join("\n", jsonLines)));
}
void givenAccountLimit(int limit) {
facade.overrideAccountLimit(AccountId.valueOf(ACCOUNT_ID), limit);
}
@Autowired
private LimitingFacade facade;
}
package io.github.mat3e.downloads.limiting;
import org.junit.jupiter.api.Tag;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Tag("integration")
@Transactional
@AutoConfigureMockMvc
@SpringBootTest
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface IntegrationTest {
}
@IntegrationTest
class LimitingControllerIntTest {
/* ... */
}
package io.github.mat3e.downloads;
import io.prometheus.client.CollectorRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.SimpleTransactionStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
@Transactional // => rollback at the end of each test
@AutoConfigureMockMvc
@SpringBootTest(properties = {
"app.repositories.type=in-mem", // not used in prod code anyhow
"app.metrics.type=in-mem",
"spring.cloud.gcp.core.enabled=false"
// ...
})
class AbstractInMemoryScenario<SELF extends AbstractInMemoryScenario<? extends SELF>> {
@Autowired
protected MockMvc mockMvc;
@SuppressWarnings("unchecked")
protected SELF given() {
return (SELF) this;
}
@SuppressWarnings("unchecked")
protected SELF when() {
return (SELF) this;
}
@SuppressWarnings("unchecked")
protected SELF and() {
return (SELF) this;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "app.metrics.type", havingValue = "in-mem")
@EnableAutoConfiguration(exclude = PrometheusMetricsExportAutoConfiguration.class)
class PrometheusTestConfiguration {
@Bean
CollectorRegistry collectorRegistry() {
return CollectorRegistry.defaultRegistry;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "app.repositories.type", havingValue = "in-mem")
class InMemoryRepositoryConfig {
/**
* Injected instances of Cleanable will be cleaned up after each test.
*/
@Bean
PlatformTransactionManager cleaningTransactionManager(List<Cleanable> toClean) {
return new PlatformTransactionManager() {
@Nonnull
@Override
public TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
return new SimpleTransactionStatus();
}
@Override
public void commit(@Nonnull TransactionStatus status) throws TransactionException {
}
@Override
public void rollback(@Nonnull TransactionStatus status) throws TransactionException {
toClean.forEach(Cleanable::clean);
}
};
}
}
@WebMvcTest
@SpringJUnitWebConfig
@DataLdapTest
@DataMongoTest
@SpringJUnitConfig
@SpringBootTest
@JsonTest
@DataJdbcTest
@DataJpaTest
@DataRedisTest
@WebMvcTest
@SpringJUnitWebConfig
@SpringJUnitConfig
@DataLdapTest
@DataMongoTest
@SpringBootTest
@JsonTest
@DataJdbcTest
@DataJpaTest
@DataRedisTest
@SpringBootTest
logging:
level:
org.springframework.test.context.cache: DEBUG
...
19:41:08.116 [Test worker] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@5eefa415 testClass = LimitingControllerIntTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.15)
2023-10-01 19:41:08.270 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : Starting LimitingControllerIntTest using Java 17.0.7 on CA042772 with PID 74526 (started by chrzonsm0401 in /Users/chrzonsm0401/IdeaProjects/downloads-service/core)
2023-10-01 19:41:08.271 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : No active profile set, falling back to 1 default profile: "default"
...
2023-10-01 19:41:09.606 INFO 74526 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 0 ms
2023-10-01 19:41:09.622 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : Started LimitingControllerIntTest in 1.484 seconds (JVM running for 2.016)
2023-10-01 19:41:09.639 INFO 74526 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@5eefa415 testClass = LimitingControllerIntTest, testInstance = io.github.mat3e.downloads.limiting.rest.LimitingControllerIntTest@7adff6cb, testMethod = illegalParams_returnsClientError@LimitingControllerIntTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.jdbc.support.JdbcTransactionManager@13ebccd]; rollback [true]
Field error in object 'asset' on field 'id': rejected value [ ]; codes [NotBlank.asset.id,NotBlank.id,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [asset.id,id]; arguments []; default message [id]]; default message [must not be blank]
...
...
19:41:08.116 [Test worker] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@5eefa415 testClass = LimitingControllerIntTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.15)
2023-10-01 19:41:08.270 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : Starting LimitingControllerIntTest using Java 17.0.7 on CA042772 with PID 74526 (started by chrzonsm0401 in /Users/chrzonsm0401/IdeaProjects/downloads-service/core)
2023-10-01 19:41:08.271 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : No active profile set, falling back to 1 default profile: "default"
...
2023-10-01 19:41:09.606 INFO 74526 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 0 ms
2023-10-01 19:41:09.622 INFO 74526 --- [ Test worker] i.g.m.d.l.r.LimitingControllerIntTest : Started LimitingControllerIntTest in 1.484 seconds (JVM running for 2.016)
2023-10-01 19:41:09.624 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Storing ApplicationContext [1779219567] in cache under key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.624 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 0, missCount = 1]
2023-10-01 19:41:09.630 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.630 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 1, missCount = 1]
2023-10-01 19:41:09.632 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.632 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 2, missCount = 1]
2023-10-01 19:41:09.636 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.636 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 3, missCount = 1]
2023-10-01 19:41:09.639 INFO 74526 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@5eefa415 testClass = LimitingControllerIntTest, testInstance = io.github.mat3e.downloads.limiting.rest.LimitingControllerIntTest@7adff6cb, testMethod = illegalParams_returnsClientError@LimitingControllerIntTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.jdbc.support.JdbcTransactionManager@13ebccd]; rollback [true]
2023-10-01 19:41:09.640 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.640 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 4, missCount = 1]
2023-10-01 19:41:09.641 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.640 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 5, missCount = 1]
2023-10-01 19:41:09.806 DEBUG 74715 --- [ Test worker] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1779219567] from cache with key [[WebMergedContextConfiguration@181d7f28 testClass = LimitingControllerIntTest, locations = '{}', classes = '{class io.github.mat3e.downloads.DownloadsApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@630cb4a4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5357c287, [ImportsContextCustomizer@78d50a3c key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5b080f3a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6f603e89, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@64df9a61, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@379614be], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2023-10-01 19:41:09.806 DEBUG 74715 --- [ Test worker] org.springframework.test.context.cache : Spring test ApplicationContext cache statistics: [DefaultContextCache@10fc1a22 size = 1, maxSize = 32, parentContextCount = 0, hitCount = 6, missCount = 1]
Field error in object 'asset' on field 'id': rejected value [ ]; codes [NotBlank.asset.id,NotBlank.id,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [asset.id,id]; arguments []; default message [id]]; default message [must not be blank]
...
Spring test ApplicationContext cache statistics:
[DefaultContextCache@10fc1a22
size = 1,
maxSize = 32,
parentContextCount = 0,
hitCount = 0,
missCount = 1
]
// SpringExtension
public static ApplicationContext getApplicationContext(ExtensionContext context) {
return getTestContextManager(context).getTestContext().getApplicationContext();
// DefaultTestContext -> this.cacheAwareContextLoaderDelegate.loadContext(...)
}
// DefaultCacheAwareContextLoaderDelegate
static final ContextCache defaultContextCache = new DefaultContextCache();
// DefaultContextCache
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
Collections.synchronizedMap(new LruCache(32, 0.75f));
MergedContextConfiguration
@TestPropertySource
@DynamicPropertySource
@ActiveProfiles
@ContextConfiguration
@MockBean
@SpyBean
@DirtiesContext
grep
po testach - @MockBean
, @SpyBean
, etc.
grep
po propsach (@TestPropertySource
itp.)
@Lazy
spring:
main:
lazy-initialization: true
@Conditional
wymaga zawołania Bean
a
static
elementy jako Bean
(~
Spring Security)
CacheMetricsRegistrar
i podobne jawnie wstrzykiwane w teście
@Lazy
debug: true
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnClass found required classes 'org.springframework.context.annotation.EnableAspectJAutoProxy', 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice', 'org.aspectj.weaver.AnnotatedElement' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
...
GET /actuator/beans
@Scheduled
(workery)@Lazy
java -javaagent:./spring-startup-analyzer/lib/spring-profiler-agent.jar \
-jar project-demo.jar
JedisException
i zerwanych połączeńtestcontainers.reuse.enable=true
@Lazy
![]() |
![]() |
Procesy | Wątki |
Klasy | Klasy lub metody |
OutOfMemoryError (za dużo wątków) |
JVM na koniec świata i jeszcze dalej |
Odpalenie wszystkiego równolegle, ciężko kontrolować | W(y)łączanie z użyciem @Isolated i @Execution |
# src/integration/resources/junit-platform.properties
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent
@Execution(ExecutionMode.CONCURRENT)
class ConcurrentTest extends IntegrationTest { // ...
@Isolated
// @ResourceLock("org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY")
class IsolatedTest extends IntegrationTest { // ...
static
@Lazy
⚠️ locki Gradle'a ⚠️
:compileIntegrationClasses
)
stage('Tests') {
stage('Unit tests') {
steps {
sh "./gradlew test"
}
}
stage('Integration tests') {
steps {
sh "./gradlew integrationTest"
}
}
stage('Integration tests intl') {
steps {
sh "./gradlew integrationTestIntl"
}
}
// ...
}
stage('Tests') {
parallel {
stage('Unit tests') {
steps {
sh "./gradlew test"
}
}
stage('Integration tests') {
steps {
sh "./gradlew integrationTest"
}
}
stage('Integration tests intl') {
steps {
sh "./gradlew integrationTestIntl"
}
}
// ...
}
}
@Lazy