AssertJ Shorts №1
Как и обещал, пост про мою любимую библиотеку для тестов - AssertJ. У проекта есть официальный веб-сайт https://assertj.github.io/doc/, он большой, красивый и там есть масса примеров, как круто можно использовать AssertJ.
Вообще, в JUnit есть и своя библиотека assertion-ов, которая неплохо расширяется, например, Hamcrest-ом. Из плюсов - это просто и доступно из коробки, из минусов - недостаточно выразительно. Что доступно в JUnit:
В билиотеке JUnit есть еще несколько assert-ов, но все они вида assertEqual/assertNotEqual/assertTrue. В AssertJ выбор гораздо больше и все начинается с метода assertThat:
И дальше уже можно по цепочке добавлять проверки. В зависимости от типа параметра доступны различные вспомогательные методы, например, для целых чисел проверки уже другие:
Есть удобная поддержка Optional-ов:
Проверка сложных объектов с AssertJ - это песня! Например, нужно сделать несколько проверок сразу и только если все проходят, только тогда объект удовлетворяет условиям:
Работа с исключениями в AssertJ - просто песня:
А какую библиотеку используете вы в своих проектах?
Как и обещал, пост про мою любимую библиотеку для тестов - AssertJ. У проекта есть официальный веб-сайт https://assertj.github.io/doc/, он большой, красивый и там есть масса примеров, как круто можно использовать AssertJ.
Вообще, в JUnit есть и своя библиотека assertion-ов, которая неплохо расширяется, например, Hamcrest-ом. Из плюсов - это просто и доступно из коробки, из минусов - недостаточно выразительно. Что доступно в JUnit:
String name = "assertJ”;
assertEquals(“assertJ”, name);
В билиотеке JUnit есть еще несколько assert-ов, но все они вида assertEqual/assertNotEqual/assertTrue. В AssertJ выбор гораздо больше и все начинается с метода assertThat:
import static org.assertj.core.api.Assertions.*;
@Test
void basic_assertions() {
String name = "assertj";
assertThat(name)
.startsWith("asser”)
.endsWith("tj")
.isEqualToIgnoringCase("ASSERTJ")
.doesNotContain("mockito");
}
И дальше уже можно по цепочке добавлять проверки. В зависимости от типа параметра доступны различные вспомогательные методы, например, для целых чисел проверки уже другие:
@Test
void number_example() {
assertThat(42)
.isGreaterThan(10)
.isLessThanOrEqualTo(100)
.isNotZero();
}
Есть удобная поддержка Optional-ов:
@Test
void optional_example() {
Optional<String> value = Optional.of("hello");
assertThat(value)
.isPresent()
.contains("hello");
}
@Test
void optional_empty() {
Optional<String> value = Optional.empty();
assertThat(value)
.isEmpty();
}
@Test
void optional_value() {
Object content = new Object();
Optional<Object> value = Optional.of(content);
assertThat(value)
.hasValue(content);
}
Проверка сложных объектов с AssertJ - это песня! Например, нужно сделать несколько проверок сразу и только если все проходят, только тогда объект удовлетворяет условиям:
@Test
void custom_check() {
User user = new User("Alex", 30);
assertThat(user).satisfies(u -> {
assertThat(u.name).startsWith("A");
assertThat(u.age).isBetween(18, 60);
});
}
Работа с исключениями в AssertJ - просто песня:
@Test
void exception_example() {
assertThatThrownBy(() -> {
throw new IllegalArgumentException("bad input");
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("bad");
}
А какую библиотеку используете вы в своих проектах?
🔥11👍2❤1
AssertJ Shorts №2 - Collections
Продолжим про AssertJ. Уверен, за неделю вы уже смогли попробовать библиотеку в деле, поэтому расскажу о других интересных возможностях, которые не бросаются в глаза при чтении документации.
Начнем с моей любимой фичи - удобной работе с коллекциями.
Если у вас есть список объектов и вы хотите проверить их свойства — не надо писать stream().map(...). Просто extracting.
Можно вытаскивать сразу несколько полей:
И даже выполнять преобразование на извлеченных значениях:
В моей практике часто нужно проверять и коллекцию из единственного элемента. Здесь у AssertJ есть несколько полезных методов, например, если в коллекции должен быть только один элемент, то к нему можно напрямую обратиться через singleElement:
Если элемент нужно проверить на соблюдение нескольких условий, то можно использовать singleElementSatisfies:
Как быть, если в коллекции есть несколько разношерстных элементов, причем все должны удовлетворять определенному условию, несколько - отдельному условию, и ни один - третьем. И здесь у AssertJ есть отличный набор вспомогательных методов - allMatch, anyMatch и noneMatch:
Ну и напоследок еще несколько полезных методов по работе с коллекциями. Например, проверка размера коллекции, содержимого и порядка элементов в ней:
Коллекцию перед проверкой можно и отфильтровать (и по значению поля тоже):
В заключительной части расскажу о том, как сделать свой собственный custom assert.
Продолжим про AssertJ. Уверен, за неделю вы уже смогли попробовать библиотеку в деле, поэтому расскажу о других интересных возможностях, которые не бросаются в глаза при чтении документации.
Начнем с моей любимой фичи - удобной работе с коллекциями.
Если у вас есть список объектов и вы хотите проверить их свойства — не надо писать stream().map(...). Просто extracting.
record User(String name, int age) {}
@Test
void extracting_example() {
List<User> users = List.of(
new User("Anna", 25),
new User("Bob", 30)
);
assertThat(users)
.extracting(User::name)
.containsExactly("Anna", "Bob");
}
Можно вытаскивать сразу несколько полей:
assertThat(users)
.extracting("name", "age")
.containsExactly(
tuple("Anna", 25),
tuple("Bob", 30)
);
И даже выполнять преобразование на извлеченных значениях:
assertThat(users)
.extracting(u -> u.name.toUpperCase())
.contains("ANNA", "BOB");
В моей практике часто нужно проверять и коллекцию из единственного элемента. Здесь у AssertJ есть несколько полезных методов, например, если в коллекции должен быть только один элемент, то к нему можно напрямую обратиться через singleElement:
@Test
void single_element_example() {
List<String> names = List.of("Alice");
assertThat(names)
.singleElement()
.isEqualTo("Alice");
}
Если элемент нужно проверить на соблюдение нескольких условий, то можно использовать singleElementSatisfies:
@Test
void single_element_satisfies_example() {
List<User> users = List.of(new User("Ann", 21));
assertThat(users).singleElementSatisfies(user -> {
assertThat(user.name).startsWith("A");
assertThat(user.age).isGreaterThan(18);
});
}
Как быть, если в коллекции есть несколько разношерстных элементов, причем все должны удовлетворять определенному условию, несколько - отдельному условию, и ни один - третьем. И здесь у AssertJ есть отличный набор вспомогательных методов - allMatch, anyMatch и noneMatch:
@Test
void any_match_example() {
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.anyMatch(name -> name.length() == 3); // Bob
}
@Test
void all_match_example() {
List<String> names = List.of("Anna", "Alan");
assertThat(names)
.allMatch(name -> name.startsWith("A"));
}
@Test
void none_match_example() {
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.noneMatch(name -> name.length() > 10); // никто не слишком длинный
}
Ну и напоследок еще несколько полезных методов по работе с коллекциями. Например, проверка размера коллекции, содержимого и порядка элементов в ней:
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.hasSize(3)
.contains("Bob", "Anna")
.containsExactly("Anna", "Bob", "Charlie") // и порядок важен!
.doesNotContain("Diana");
Коллекцию перед проверкой можно и отфильтровать (и по значению поля тоже):
List<String> names = List.of("Anna", "Bob", "Alice");
assertThat(names)
.filteredOn(name -> name.startsWith("A"))
.containsExactly("Anna", "Alice");
В заключительной части расскажу о том, как сделать свой собственный custom assert.
🔥10👍4
AssertJ Shorts №3 - Custom Assertion
Кастомные assertions — одна из сильных сторон AssertJ. Они позволяют писать тесты, которые выглядят как читаемый DSL, например:
Разберем по шагам, как сделать собственный assertion. Для начала смоделируем класс, который будем в дальнейшем тестировать:
А теперь приступим как самому assertion - создаем класс, наследуем его от
Здесь (1) - конструктор, который принимает проверяемый объект, а (2) - первый вызов
И в тестах теперь можно написать:
Кроме собственных assertion-ов можно еще создать condition, который используется для проверки элементов коллекции. Подойдет, если одно и то же условие используется во многих местах:
А,
Если условие используется только один раз, то можно использовать match:
О каких еще возможностях библиотеки assertJ вы хотели бы рассказать?
Кастомные assertions — одна из сильных сторон AssertJ. Они позволяют писать тесты, которые выглядят как читаемый DSL, например:
assertThat(user).hasValidEmail().isAdult();
Разберем по шагам, как сделать собственный assertion. Для начала смоделируем класс, который будем в дальнейшем тестировать:
@Value
public class User {
private final String name;
private final String email;
private final int age;
}
А теперь приступим как самому assertion - создаем класс, наследуем его от
AbstractAssert, а в качестве параметров типов указываем класс assertion-а и тот класс, который assertion должен проверять:
import org.assertj.core.api.AbstractAssert;
public class UserAssert extends AbstractAssert<UserAssert, User> {
// (1)
public UserAssert(User actual) {
super(actual, UserAssert.class);
}
// (2)
public static UserAssert assertThat(User actual) {
return new UserAssert(actual);
}
}
Здесь (1) - конструктор, который принимает проверяемый объект, а (2) - первый вызов
assertThat, который создаст экземпляр assertion-а. Теперь остается только добавить методы для проверки:
import org.assertj.core.api.AbstractAssert;
public class UserAssert extends AbstractAssert<UserAssert, User> {
// конструктор и assertThat должны быть здесь
public UserAssert hasValidEmail() {
isNotNull();
if (actual.getEmail() == null || !actual.getEmail().contains("@")) {
failWithMessage("Expected user to have valid email, but was <%s>", actual.getEmail());
}
return this;
}
public UserAssert isAdult() {
isNotNull();
if (actual.getAge() < 18) {
failWithMessage("Expected user to be adult (18+), but was <%s>", actual.getAge());
}
return this;
}
}
И в тестах теперь можно написать:
import static your.package.UserAssert.assertThat;
@Test
void custom_assertion_example() {
User user = new User("Alex", "alex@example.com", 20);
assertThat(user)
.hasValidEmail()
.isAdult();
}
Кроме собственных assertion-ов можно еще создать condition, который используется для проверки элементов коллекции. Подойдет, если одно и то же условие используется во многих местах:
@Test
void condition_example() {
User user = new User("Alice", "alice@example.com", 20);
assertThat(user).has(isAdult);
List<User> users = List.of(
new User("Bob", "bob@texample.com", 17),
new User("Alice", "alice@example.com", 20)
);
assertThat(users).haveExactly(1, isAdult); // один взрослый
}
А,
isAdult в этом случае - отдельный класс:
import org.assertj.core.api.Condition;
Condition<User> isAdult = new Condition<>(
user -> user.age() >= 18,
"an adult (18+)"
);
Если условие используется только один раз, то можно использовать match:
assertThat(user)
.matches(u -> u.age() >= 18, "age is 18+");
О каких еще возможностях библиотеки assertJ вы хотели бы рассказать?
🔥5👍4
AssertJ Shorts №4 - Recursive Comparison и Factories
Как верно заметили в комментариях, я не рассказал еще про некоторые важные фишки AssertJ, такие как
Иногда обычный
Он позволяет сравнивать объекты рекурсивно, т.е. по всем вложенным полям, даже если это разные экземпляры одного класса.
Без
Кроме рекурсивного сравнения,
Еще более интересная возможность - сравнивать списки объектов через
Некоторые поля нужно сравнивать по особым правилам, например,
Здесь
Если одни и те же настройки сравнения используются несколько раз, их можно вытащить в отдельную конфигурацию и использовать несколько раз:
И кратко про
Чтобы сохранить тип и использовать нужные ассёрты, можно использовать
А теперь с
Как верно заметили в комментариях, я не рассказал еще про некоторые важные фишки AssertJ, такие как
usingRecursiveComparison и InstanceOfAssertFactory. Спасибо, что заметили, исправляюсь!Иногда обычный
assertThat(actual).isEqualTo(expected) не работает — например, если сравниваемые объекты содержат вложенные поля, коллекции или даже циклические ссылки. В таких случаях на помощь приходит usingRecursiveComparison().Он позволяет сравнивать объекты рекурсивно, т.е. по всем вложенным полям, даже если это разные экземпляры одного класса.
record Address(String city, String zip) {}
record User(String name, Address address) {}
@Test
void recursive_comparison_simple() {
User actual = new User("Alice", new Address("London", "12345"));
User expected = new User("Alice", new Address("London", "12345"));
assertThat(actual)
.usingRecursiveComparison()
.isEqualTo(expected);
}
Без
usingRecursiveComparsion тест бы упал, так как объекты Address разные. Кроме рекурсивного сравнения,
usingRecursiveComparison позволяет исключать некоторые поля из сравнения. Особенно полезно, например, для полей типа createdAt и updatedAt, значения которых точно не совпадают у разных объектов:
record User(String name, int age) {}
@Test
void ignore_field() {
User actual = new User("Alice", 30);
User expected = new User("Alice", 99); // возраст не важен
assertThat(actual)
.usingRecursiveComparison()
.ignoringFields("age")
.isEqualTo(expected);
}
Еще более интересная возможность - сравнивать списки объектов через
usingRecursiveFieldByFieldElementComparator:
List<User> actual = List.of(new User("Alice", new Address("London", "12345")));
List<User> expected = List.of(new User("Alice", new Address("London", "12345")));
assertThat(actual)
.usingRecursiveFieldByFieldElementComparator()
.containsExactlyElementsOf(expected);
Некоторые поля нужно сравнивать по особым правилам, например,
BigDecimal с учетом или без учета точности. Здесь помогут withComparatorForFields и withEqualsForType:
record Price(BigDecimal amount) {}
@Test
void with_comparator_for_field() {
Price actual = new Price(new BigDecimal("10.005"));
Price expected = new Price(new BigDecimal("10.004"));
Comparator<BigDecimal> roundedComparator =
Comparator.comparing(bd -> bd.setScale(2, RoundingMode.HALF_UP));
assertThat(actual)
.usingRecursiveComparison()
.withComparatorForFields(roundedComparator, "amount")
.isEqualTo(expected);
}
Здесь
amount сравнивается уже после округления до двух знаков после запятой. Если одни и те же настройки сравнения используются несколько раз, их можно вытащить в отдельную конфигурацию и использовать несколько раз:
RecursiveComparisonConfiguration config = RecursiveComparisonConfiguration.builder()
.withIgnoredFields("id", "timestamp")
.withIgnoreAllActualNullFields(true)
.build();
assertThat(actual)
.usingRecursiveComparison(config)
.isEqualTo(expected);
И кратко про
InstanceOfAssertFactory - совсем забыл, насколько крутая это фича. В обычных ассёртах мы можем легко проверять свойства объектов, но при использовании методов вроде extracting() или filteredOn() тип выводится как Object, и AssertJ не знает, какие методы доступны у результата.Чтобы сохранить тип и использовать нужные ассёрты, можно использовать
InstanceOfAssertFactory. Без InstanceOfAssertFactory:
List<Person> people = List.of(new Employee("Alice", "HR"), new Manager("Bob", 5));
assertThat(people)
.filteredOn(p -> p instanceof Manager)
.first() // тип Object
.extracting("teamSize") // работает, но не типобезопасно
.isEqualTo(5);
А теперь с
InstanceOfAssertFactory:❤2
InstanceOfAssertFactory<Manager, ObjectAssert<Manager>> managerAssert =
new InstanceOfAssertFactory<>(Manager.class, Assertions::assertThat);
assertThat(people)
.filteredOn(p -> p instanceof Manager)
.first(managerAssert) // теперь тип — Manager
.extracting(Manager::getTeamSize)
.isEqualTo(5);
Если ты часто используешь типы вроде String, Integer, Map, List, LocalDateTime и т.д., то нет необходимости создавать InstanceOfAssertFactory вручную — AssertJ уже поставляет готовые!
import static org.assertj.core.api.InstanceOfAssertFactories.*;
@Test
void use_predefined_instance_of_assert_factory() {
record Event(String type, Map<String, Object> payload) {}
var events = List.of(
new Event("click", Map.of("x", 10, "y", 20)),
new Event("submit", Map.of("formId", "abc123"))
);
assertThat(events)
.element(0)
.extracting(Event::payload, MAP) // используем готовую фабрику
.containsEntry("x", 10);
}
Спасибо большое за комментарии, пишите еще!
🔥10👍3
Awaility Shorts
Мне кажется, про AssertJ я рассказал все, что знал и использовал сам каждый день. Перейдем к следующей библиотеке, которая мне также помогает каждый день - Awaitility. Если есть необходимость проверить результаты работы, которая происходит в фоне, то Awaitility здесь просто незаменима. Естественно, всегда есть возможность сделать
Например, здесь тест ждет до трех секунд, пока значение
Условие можно передать и лямбдой:
DSL Awaitility включает еще несколько очень полезных методов, например,
Иногда проверяемый метод может бросать исключения до момента полной инициализации. Эти исключения можно игнорировать через
Также можно игнорировать исключения только определенных типов:
И что я люблю больше всего, так это интеграцию с AssertJ через
Используете ли вы библиотеку Awaility в своей работе?
Мне кажется, про AssertJ я рассказал все, что знал и использовал сам каждый день. Перейдем к следующей библиотеке, которая мне также помогает каждый день - Awaitility. Если есть необходимость проверить результаты работы, которая происходит в фоне, то Awaitility здесь просто незаменима. Естественно, всегда есть возможность сделать
Thread.sleep(1000) в цикле, но Awaitility предлагает гораздо более удобный DSL. Например, здесь тест ждет до трех секунд, пока значение
flag сменится на true. Если за три секунду этого не произойдет - тест упадет.
import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
@Test
void shouldWaitUntilValueIsSet() {
AtomicBoolean flag = new AtomicBoolean(false);
Executors.newSingleThreadScheduledExecutor()
.schedule(() -> flag.set(true), 1, SECONDS);
await().atMost(3, SECONDS)
.untilTrue(flag);
}
Условие можно передать и лямбдой:
await().atMost(2, SECONDS)
.until(() -> repository.findAll().size() == 3);
DSL Awaitility включает еще несколько очень полезных методов, например,
pollInterval - как часто проверять:
await()
.pollInterval(200, MILLISECONDS)
.atMost(5, SECONDS)
.until(() -> repo.isInitialized());
atLeast - минимальное время ожидания, даже если условие будет выполнено сразу, то тест все равно подождет указанное в atLeast время:
await()
.atLeast(1, SECONDS) // в любом случае ждем одну секунду
.atMost(5, SECONDS)
.until(() -> cache.isWarm());
Иногда проверяемый метод может бросать исключения до момента полной инициализации. Эти исключения можно игнорировать через
ignoreException:
await()
.ignoreExceptions()
.atMost(2, SECONDS)
.until(() -> service.getStatus().equals("READY"));
Также можно игнорировать исключения только определенных типов:
await()
.ignoreException(IllegalStateException.class)
.until(() -> client.getConnection().isAlive());
И что я люблю больше всего, так это интеграцию с AssertJ через
untilAsserted:
await()
.atMost(3, SECONDS)
.untilAsserted(() -> {
assertThat(store.getData()).isNotEmpty();
assertThat(store.getData()).containsExactly("hello");
});
Используете ли вы библиотеку Awaility в своей работе?
🔥7👍4
Про AssertJ поговорили, про Mockito тоже поговорили, даже Awaitility затронули. Значит, пришло время поговорить про WireMock!
WireMock — это инструмент, который поднимает локальный HTTP-сервер прямо внутри теста (или отдельным процессом) и позволяет:
• эмулировать внешние API;
• контролировать ответы (и их задержки);
• проверять, что запросы отправлялись с правильными параметрами;
• работать без реального интернета.
Минимальный пример без расширений для JUnit и другой автоматизации:
Существенную часть здесь занимает настройка самого WireMock-а, которую можно существенно упростить, если добавить расширение для JUnit:
Мне очень нравятся библиотеки, в которых API читаемый. Мне кажется, WireMock в этом плане - одна из лучших библиотек, настраивать сам WireMock одно удовольствие. Настраивать ответы, которые WireMock выдает уже не так просто, но API довольно выразительный и старается помочь изо всех сил.
Самый простой вариант - отвечать заранее заготовленным текстом и статусом:
Отвечать ошибкой:
Отвечать с задержкой:
И, наконец, динамическая генерация ответа на основе данных из запроса:
Кроме настройки запросов, WireMock позволяет проверять, какой именно запрос пришел (вдруг, не все параметры указаны). Самые простые варианты:
Как и в случае с Mockito, можно проверять, сколько было запросов:
Можно проверить наличие заголовка, например, что указан правильный content type:
WireMock — это инструмент, который поднимает локальный HTTP-сервер прямо внутри теста (или отдельным процессом) и позволяет:
• эмулировать внешние API;
• контролировать ответы (и их задержки);
• проверять, что запросы отправлялись с правильными параметрами;
• работать без реального интернета.
Минимальный пример без расширений для JUnit и другой автоматизации:
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class WireMockExampleTest {
private WireMockServer wireMockServer;
@BeforeEach
void setup() {
wireMockServer = new WireMockServer(8089);
wireMockServer.start();
configureFor("localhost", 8089);
stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello from WireMock!")));
}
@AfterEach
void tearDown() {
wireMockServer.stop();
}
@Test
void shouldReturnStubbedResponse() {
String response = new RestTemplate()
.getForObject("http://localhost:8089/hello", String.class);
assertThat(response).isEqualTo("Hello from WireMock!");
}
}
Существенную часть здесь занимает настройка самого WireMock-а, которую можно существенно упростить, если добавить расширение для JUnit:
public class WireMockJUnit5Test {
// вот здесь расширение и регистрируется
@RegisterExtension
static WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig().port(8089))
.build();
// обычный тест
@Test
void shouldReturnStubbedResponse() {
wireMock.stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello JUnit 5 + WireMock!")));
String response = new RestTemplate()
.getForObject("http://localhost:8089/hello", String.class);
assertThat(response).isEqualTo("Hello JUnit 5 + WireMock!");
wireMock.verify(getRequestedFor(urlEqualTo("/hello")));
}
}
Мне очень нравятся библиотеки, в которых API читаемый. Мне кажется, WireMock в этом плане - одна из лучших библиотек, настраивать сам WireMock одно удовольствие. Настраивать ответы, которые WireMock выдает уже не так просто, но API довольно выразительный и старается помочь изо всех сил.
Самый простой вариант - отвечать заранее заготовленным текстом и статусом:
stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello from WireMock!")));
Отвечать ошибкой:
stubFor(get("/error")
.willReturn(serverError())); // статус 500
Отвечать с задержкой:
stubFor(get("/slow")
.willReturn(aResponse()
.withFixedDelay(2000) // задержка 2 секунды
.withStatus(200)
.withBody("Delayed!")));
И, наконец, динамическая генерация ответа на основе данных из запроса:
stubFor(get(urlMatching("/user/.*"))
.willReturn(aResponse()
.withStatus(200)
.withTransformers("response-template")
.withBody("Hello {{request.path.[1]}}!")));
Кроме настройки запросов, WireMock позволяет проверять, какой именно запрос пришел (вдруг, не все параметры указаны). Самые простые варианты:
// проверим, что пришел GET запрос
verify(getRequestedFor(urlEqualTo("/hello")));
// проверим, что пришел POST запрос
verify(postRequestedFor(urlEqualTo("/api/data")));
Как и в случае с Mockito, можно проверять, сколько было запросов:
verify(2, getRequestedFor(urlEqualTo("/hello")));
Можно проверить наличие заголовка, например, что указан правильный content type:
verify(postRequestedFor(urlEqualTo("/submit"))
.withHeader("Content-Type", equalTo("application/json")));
🔥8👍3
Или, что в теле запроса есть JSON в определенном формате:
Аналогично, можно проверять и GET запросы на наличие параметров:
Какими еще возможностями WireMock вы пользуетесь?
verify(postRequestedFor(urlEqualTo("/submit"))
.withRequestBody(matchingJsonPath("$.user.id", equalTo("123"))));
Аналогично, можно проверять и GET запросы на наличие параметров:
verify(getRequestedFor(urlPathEqualTo("/list"))
.withQueryParam("page", equalTo("3")));
Какими еще возможностями WireMock вы пользуетесь?
🔥8👍4
Test Containers Shorts
Test Containers — это библиотека для запуска Docker-контейнеров прямо из тестов и сегодня будет короткая заметка об этой библиотеке.
Для примера запустим PostgreSQL прямо из теста (пока без интеграции с JUnit):
Для JUnit есть расширение, которое автоматически запускает и останавливает контейнеры до и после выполнения теста:
Test Containers довольно удобно интегрируются со спринговыми тестами через
А вы используете Test Containers в своей работе?
Test Containers — это библиотека для запуска Docker-контейнеров прямо из тестов и сегодня будет короткая заметка об этой библиотеке.
Для примера запустим PostgreSQL прямо из теста (пока без интеграции с JUnit):
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import static org.assertj.core.api.Assertions.assertThat;
class PostgresTest {
// Поднимаем контейнер PostgreSQL
static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
static {
postgres.start();
}
@Test
void testDatabaseConnection() throws Exception {
var connection = postgres.createConnection("");
var result = connection.createStatement().executeQuery("SELECT 1");
assertThat(result.next()).isTrue();
assertThat(result.getInt(1)).isEqualTo(1);
}
}
Для JUnit есть расширение, которое автоматически запускает и останавливает контейнеры до и после выполнения теста:
@Testcontainers
class JUnit5Test {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Test
void testIsRunning() {
assertThat(postgres.isRunning()).isTrue();
}
}
Test Containers довольно удобно интегрируются со спринговыми тестами через
@DynamicPropertySource:
@SpringBootTest
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testSaveAndFindUser() {
User user = new User(null, "Alice");
userRepository.save(user);
var users = userRepository.findAll();
assertThat(users).hasSize(1);
assertThat(users.get(0).getName()).isEqualTo("Alice");
}
}
А вы используете Test Containers в своей работе?
👍10🔥6
Media is too big
VIEW IN TELEGRAM
Hibernate vs jOOQ vs SQL - Engineering Trade-offs
Hibernate долго был для меня инструментом по умолчанию в Java-разработке — быстрый, удобный, “магический”. Но со временем я стал всё чаще замечать его ограничения и переходить к jOOQ или plain SQL.
В этом видео мой взгляд на ORM глазами инженера, который работал с реальными системами: где Hibernate помогает, а где начинает усложнять жизнь.
Hibernate долго был для меня инструментом по умолчанию в Java-разработке — быстрый, удобный, “магический”. Но со временем я стал всё чаще замечать его ограничения и переходить к jOOQ или plain SQL.
В этом видео мой взгляд на ORM глазами инженера, который работал с реальными системами: где Hibernate помогает, а где начинает усложнять жизнь.
🔥14❤2
Media is too big
VIEW IN TELEGRAM
Hibernate - Спасение или проблема?
Hibernate — это мощный ORM, который идеально раскрывается в бизнес-приложениях: CRUD, сложные доменные модели, связи между сущностями и быстрый старт разработки без постоянного написания SQL.
В этом видео разбираем:
— где Hibernate реально помогает
— почему он так хорош для CRUD и агрегатов
— как работает lazy loading и где он может «стрельнуть в ногу»
— когда JPQL уже не хватает и приходится идти в native SQL
— и где проходит граница между удобством ORM и контролем над базой
А как ты используешь Hibernate в своих проектах?
Hibernate — это мощный ORM, который идеально раскрывается в бизнес-приложениях: CRUD, сложные доменные модели, связи между сущностями и быстрый старт разработки без постоянного написания SQL.
В этом видео разбираем:
— где Hibernate реально помогает
— почему он так хорош для CRUD и агрегатов
— как работает lazy loading и где он может «стрельнуть в ногу»
— когда JPQL уже не хватает и приходится идти в native SQL
— и где проходит граница между удобством ORM и контролем над базой
А как ты используешь Hibernate в своих проектах?
🔥7
Media is too big
VIEW IN TELEGRAM
Почему я всё реже выбираю Hibernate
В этом видео расскажу, где ORM создаёт больше проблем, чем решает, когда стоит использовать чистый SQL и почему jOOQ стал моим инструментом по умолчанию для работы с базой данных в Java.
В этом видео расскажу, где ORM создаёт больше проблем, чем решает, когда стоит использовать чистый SQL и почему jOOQ стал моим инструментом по умолчанию для работы с базой данных в Java.
👍8❤7
Media is too big
VIEW IN TELEGRAM
MCP сервер для Codex/Claude Code на Spring Boot
Создаём MCP Server на Spring Boot и подключаем его к Codex! В этом видео AI получит доступ к коллекции винила: поиск, просмотр и добавление пластинок через MCP. 🚀💿
Создаём MCP Server на Spring Boot и подключаем его к Codex! В этом видео AI получит доступ к коллекции винила: поиск, просмотр и добавление пластинок через MCP. 🚀💿
🔥12❤2👍2
Media is too big
VIEW IN TELEGRAM
MCP клиент на Spring Boot - подключаемся к Ollama и MCP Server
В этом видео собираем MCP Client на Spring Boot и подключаем его к локально запущенному Ollama и MCP серверу на Spring Boot.
MCP Server предоставляет инструменты для управления коллекцией виниловых пластинок и его мы уже делали в прошлом видео.
Как сейчас модно говорить - без маркетинговой шелухи, четко и по делу, только Spring Boot, только стартеры и минимум кода.
В этом видео собираем MCP Client на Spring Boot и подключаем его к локально запущенному Ollama и MCP серверу на Spring Boot.
MCP Server предоставляет инструменты для управления коллекцией виниловых пластинок и его мы уже делали в прошлом видео.
Как сейчас модно говорить - без маркетинговой шелухи, четко и по делу, только Spring Boot, только стартеры и минимум кода.
🔥8❤3
Media is too big
VIEW IN TELEGRAM
AI-агент теперь ПОМНИТ всё! Добавляем память в Spring AI
В прошлом видео мы научили AI работать с MCP Server и коллекцией виниловых пластинок. Но была одна проблема — после каждого сообщения агент забывал весь предыдущий разговор.
В этом видео исправим это с помощью ChatMemory из Spring AI. Теперь AI будет помнить историю диалога, учитывать контекст и отвечать намного естественнее.
В прошлом видео мы научили AI работать с MCP Server и коллекцией виниловых пластинок. Но была одна проблема — после каждого сообщения агент забывал весь предыдущий разговор.
В этом видео исправим это с помощью ChatMemory из Spring AI. Теперь AI будет помнить историю диалога, учитывать контекст и отвечать намного естественнее.
❤5👍5😁1
Media is too big
VIEW IN TELEGRAM
Микросервисы? Или лучше модульный монолит? | Engineering Trade-offs
Все слышали, что будущее за микросервисами. Но почему тогда Amazon, Shopify и другие компании всё чаще говорят о модульных монолитах?
В этом выпуске разбираемся:
* почему микросервисы вообще появились;
* какую цену приходится платить за распределённую систему;
* почему микросервисы часто решают организационные, а не технические проблемы;
* что такое модульный монолит и зачем нужен Spring Modulith;
* как AI меняет требования к архитектуре.
В инженерии редко бывают универсально правильные решения. Есть только trade-offs.
Все слышали, что будущее за микросервисами. Но почему тогда Amazon, Shopify и другие компании всё чаще говорят о модульных монолитах?
В этом выпуске разбираемся:
* почему микросервисы вообще появились;
* какую цену приходится платить за распределённую систему;
* почему микросервисы часто решают организационные, а не технические проблемы;
* что такое модульный монолит и зачем нужен Spring Modulith;
* как AI меняет требования к архитектуре.
В инженерии редко бывают универсально правильные решения. Есть только trade-offs.
❤11🔥3👍1
Media is too big
VIEW IN TELEGRAM
Spring Modulith - модульный монолит на Spring Boot
В этом видео разбираем Spring Modulith — новый подход к построению модульных монолитов в Spring Boot.
Вы узнаете:
• зачем вообще нужен Spring Modulith;
• как разделить приложение на независимые модули;
• как автоматически проверять архитектурные границы;
В этом видео разбираем Spring Modulith — новый подход к построению модульных монолитов в Spring Boot.
Вы узнаете:
• зачем вообще нужен Spring Modulith;
• как разделить приложение на независимые модули;
• как автоматически проверять архитектурные границы;
❤8🔥3👍2
Media is too big
VIEW IN TELEGRAM
Распиливаем монолит на микросервисы со Spring Modulith
В этом выпуске превращаем модульный монолит в полноценную микросервисную архитектуру. Покажу, какие изменения действительно нужны, как использовать Kafka и HTTP Interface Clients, и как здесь помогает Spring Modulith
В этом выпуске превращаем модульный монолит в полноценную микросервисную архитектуру. Покажу, какие изменения действительно нужны, как использовать Kafka и HTTP Interface Clients, и как здесь помогает Spring Modulith
👍7❤4
Media is too big
VIEW IN TELEGRAM
SQL vs NoSQL: История борьбы и сотрудничества - Engineering Trade-offs
В начале 2010-х все были уверены, что SQL скоро исчезнет, а будущее за NoSQL. Но прошло 15 лет, и PostgreSQL по-прежнему остается одной из самых популярных баз данных. Что пошло не так?
В этом выпуске Engineering Trade-offs разбираемся, почему появился NoSQL, какие реальные проблемы он решил, почему этого оказалось недостаточно и как SQL и NoSQL в итоге изменили друг друга.
В начале 2010-х все были уверены, что SQL скоро исчезнет, а будущее за NoSQL. Но прошло 15 лет, и PostgreSQL по-прежнему остается одной из самых популярных баз данных. Что пошло не так?
В этом выпуске Engineering Trade-offs разбираемся, почему появился NoSQL, какие реальные проблемы он решил, почему этого оказалось недостаточно и как SQL и NoSQL в итоге изменили друг друга.
👍9
Media is too big
VIEW IN TELEGRAM
Как разбить большую таблицу на партиции в PostgreSQL
Рассказываю о том, как работать с партициями в PostgreSQL. Несколько примеров:
1. Создаем таблицу с несколькими партициями сразу.
2. Мигрируем существующие данные в таблицу с партициями.
Пишите в комментариях, какой подход при миграции использовали вы.
Рассказываю о том, как работать с партициями в PostgreSQL. Несколько примеров:
1. Создаем таблицу с несколькими партициями сразу.
2. Мигрируем существующие данные в таблицу с партициями.
Пишите в комментариях, какой подход при миграции использовали вы.
🔥9
Media is too big
VIEW IN TELEGRAM
MongoDB Sharding: как разделить миллион документов между серверами
Разбираемся, как работает MongoDB Sharding: поднимаем sharded cluster, распределяем миллион документов между серверами, смотрим на chunks, balancer и разбираемся, как MongoDB выбирает нужный shard для запроса
Разбираемся, как работает MongoDB Sharding: поднимаем sharded cluster, распределяем миллион документов между серверами, смотрим на chunks, balancer и разбираемся, как MongoDB выбирает нужный shard для запроса
❤5👍2