Hamcrest содержит прекрасную библиотеку матчеров, а их комбинирование позволяет творить чудеса. А когда их возможностей не хватает, можно написать свой собственный матчер.
Мне потребовалось проверять результат формирования параметров GET запроса:
1
|
arg1=val1&arg2=val2&arg3=val3"
|
И я столкнулся с тем, что во-первых это строка, во-вторых порядок элементов в строке не важен, поэтому сравнивать проверяемую строку с образцом нельзя. Чтобы не загромождать тест дополнительными преобразованиями, я решил написать матчер, сравнивающий строки GET параметров.
Собственный матчер
Матчер должен расширять класс BaseMatcher и переопределять его методы, но мы будем использовать типобезопасную версию TypeSafeMatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
public class UrlParametersMatcher extends TypeSafeMatcher<String> {
private final String expectedString;
private final Map<String, String> expected;
protected final Map<String, String> paramStringToMap(final String string) {
return Arrays.stream(string.split("&"))
.collect(Collectors.toMap(
(String s) -> s.split("=")[0],
(String s) -> s.split("=").length==2 ? s.split("=")[1] : ""));
}
/**
* Constructs matcher.
* @param e model string to match.
*/
public UrlParametersMatcher(final String e) {
expectedString = e;
expected = paramStringToMap(expectedString);
}
@Override
protected final boolean matchesSafely(final String actualString) {
return expected.equals(paramStringToMap(actualString));
}
@Override
public final void describeTo(final Description description) {
description
.appendText("Parameters should match: ")
.appendValue(expectedString);
}
@Override
protected final void describeMismatchSafely(
final String item,
final Description mismatchDescription) {
Map<String, String> actual = paramStringToMap(item);
if (expected.size() != actual.size()) {
mismatchDescription
.appendText("Actual number of parameters is ")
.appendValue(actual.size())
.appendText(" while expecting ")
.appendValue(expected.size())
.appendText(" parameters");
return;
}
for (Map.Entry<String, String> entry: expected.entrySet()) {
if (!actual.containsKey(entry.getKey())) {
mismatchDescription
.appendText("Expected parameter ")
.appendValue(entry.getKey())
.appendText(" is not found");
return;
}
if (!entry.getValue().equals(actual.get(entry.getKey()))) {
mismatchDescription
.appendText("For parameter ")
.appendValue(entry.getKey())
.appendText(" actual value is ")
.appendValue(actual.get(entry.getKey()))
.appendText(" while expected value was ")
.appendValue(entry.getValue());
return;
}
}
}
/**
* Constructs ready-to-use matcher.
* @param parameters model string.
* @return Fully constructed matcher.
*/
public static UrlParametersMatcher
parametersEqual(final String parameters) {
return new UrlParametersMatcher(parameters);
}
}
|
В коде матчера можно выделить три основных части — обработку строки параметров, проверку и вывод результата и фабричный метод.
Обработка параметров
Образец для сравнения передаётся в конструктор. Здесь, используя paramStringToMap, его разбирают на части и сохраняют для дальнейшего использования.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private final String expectedString;
private final Map<String,String> expected;
protected Map<String,String> paramStringToMap(String string) {
return Arrays.stream(string.split("&"))
.collect(Collectors.toMap(
(String s) -> s.split("=")[0],
(String s) -> s.split("=").length==2 ? s.split("=")[1] : ""));
}
public UrlParametersMatcher(String e) {
expectedString = e;
expected = paramStringToMap(expectedString);
}
|
Функция paramStringToMap разделяет строку параметров на отдельные параметры, которые складываются в хэш. Разделение строки на параметры реализовано на streams api Java 8.
Сравнение
Главная часть матчера — сюда передаётся фактическое значение и производится сравнение:
1
2
3
4
|
@Override
protected boolean matchesSafely(String actualString) {
return expected.equals(paramStringToMap(actualString));
}
|
Тестовая строка просто переводится в хэш и сравнивается с образцом. Гораздо интереснее функции вывода результатов:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
@Override
public final void describeTo(final Description description) {
description
.appendText("Parameters should match: ")
.appendValue(expectedString);
}
@Override
protected final void describeMismatchSafely(
final String item,
final Description mismatchDescription) {
Map<String, String> actual = paramStringToMap(item);
if (expected.size() != actual.size()) {
mismatchDescription
.appendText("Actual number of parameters is ")
.appendValue(actual.size())
.appendText(" while expecting ")
.appendValue(expected.size())
.appendText(" parameters");
return;
}
for (Map.Entry<String, String> entry: expected.entrySet()) {
if (!actual.containsKey(entry.getKey())) {
mismatchDescription
.appendText("Expected parameter ")
.appendValue(entry.getKey())
.appendText(" is not found");
return;
}
if (!entry.getValue().equals(actual.get(entry.getKey()))) {
mismatchDescription
.appendText("For parameter ")
.appendValue(entry.getKey())
.appendText(" actual value is ")
.appendValue(actual.get(entry.getKey()))
.appendText(" while expected value was ")
.appendValue(entry.getValue());
return;
}
}
}
|
Функция describeTo описывает сам матчер, функция describeMismatch описывает, что матчеру не понравилось при сравнении. Содержимое этих функций и подробность отчёта целиком и полностью на совести разработчика. Я реализовал проверку всех шагов сравнения хэшей с подробным выводом различий.
Использование
Для удобства использования матчера в его код добавлен вспомогательный метод по созданию матчера:
1
2
3
|
public static UrlParametersMatcher parametersEqual(final String parameters) {
return new UrlParametersMatcher(parameters);
}
|
Теперь матчер можно использовать как:
1
|
assertThat("arg1=val1&arg2=val2&arg3=val3", parametersEqual("arg1=val1&arg2=val2&arg3=val3"));
|
Можно проверить, как выводятся ошибки:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class UrlParametersMatcherTest {
@Test
public void assertMatches() {
assertThat("arg1=val1&arg2=val2&arg3=val3", parametersEqual("arg1=val1&arg2=val2&arg3=val3"));
}
@Test
public void assertLengthMismatch() {
assertThat("arg1=val1&arg2=val2&arg3=val3&arg4=val4", parametersEqual("arg1=val1&arg2=val2&arg3=val3"));
}
@Test
public void assertNamesMismatch() {
assertThat("arg1=val1&arg22=val2&arg3=val3", parametersEqual("arg1=val1&arg2=val2&arg3=val3"));
}
@Test
public void assertValuesMismatch() {
assertThat("arg1=val1&arg2=val22&arg3=val3", parametersEqual("arg1=val1&arg2=val2&arg3=val3"));
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Tests run: 4, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec <<< FAILURE! - in org.satgate.filestream.UrlParametersMatcherTest
assertNamesMismatch(org.satgate.filestream.UrlParametersMatcherTest) Time elapsed: 0 sec <<< FAILURE!
java.lang.AssertionError:
Expected: Parameters should match: "arg1=val1&arg2=val2&arg3=val3"
but: Expected parameter "arg2" is not found
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at org.junit.Assert.assertThat(Assert.java:923)
at org.satgate.filestream.UrlParametersMatcherTest.assertNamesMismatch(UrlParametersMatcherTest.java:22)
assertValuesMismatch(org.satgate.filestream.UrlParametersMatcherTest) Time elapsed: 0 sec <<< FAILURE!
java.lang.AssertionError:
Expected: Parameters should match: "arg1=val1&arg2=val2&arg3=val3"
but: For parameter "arg2" actual value is "val22" while expected value was "val2"
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at org.junit.Assert.assertThat(Assert.java:923)
at org.satgate.filestream.UrlParametersMatcherTest.assertValuesMismatch(UrlParametersMatcherTest.java:27)
assertLengthMismatch(org.satgate.filestream.UrlParametersMatcherTest) Time elapsed: 0 sec <<< FAILURE!
java.lang.AssertionError:
Expected: Parameters should match: "arg1=val1&arg2=val2&arg3=val3"
but: Actual number of parameters is <4> while expecting <3> parameters
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at org.junit.Assert.assertThat(Assert.java:923)
at org.satgate.filestream.UrlParametersMatcherTest.assertLengthMismatch(UrlParametersMatcherTest.java:17)
|
Код примера доступен на github.