Hamcrest Matchers là gì?
Matcher là một đối số của phương thức Assert.assertThat(). Dùng để so sánh một giá trị thực tế có thõa mãn với một org.hamcrest.Matcher được xác định hay không. Với matchers có thể kiểm tra kết quả của một string, number, collections…
Hamcrest là một thư viện của các Matcher, là một tiện ích bổ sung bên ngoài của JUnit Framework và JUnit 4.12, nó cung cấp các static matchers để giúp chúng ta kiểm tra kết quả của Unit Test đơn giản hơn và dễ đọc hơn.
Ví dụ các cách viết dưới đây là tương đương giữa sử dụng các phương thức có sẵn của JUnit 4 và sử dụng thư viện Hamcrest:
// JUnit 4 for equals check assertEquals(expected, actual); // Hamcrest for equals check assertThat(actual, is(equalTo(expected)));
// JUnit 4 for not equals check assertNotEquals(expected, actual) // Hamcrest for not equals check assertThat(actual, is(not(equalTo(expected))));
Sử dụng Hamcrest Matchers
Các loại Matcher
Bên dưới là một vài phương thức hỗ trợ của Matcher:
- Core
- anything : luôn trùng khớp, được sử dụng khi chúng ta không quan tâm đối tượng đang test là gì.
- describedAs : được sử dụng để thêm mô tả thất bại tùy chỉnh.
- is : để cải thiện khả năng đọc hiểu test case. Xem thêm phần “Sugar” bên dưới.
- Logical
- allOf : trùng khớp nếu tất cả các matcher khớp nhau, nó tương tự như toán tử điều kiện “&&” trong Java.
- anyOf : trùng khớp nếu bất kỳ matcher khớp nhau, nó tương tự như toán tử điều kiện “||” trong Java.
- not : trùng khớp nếu KHÔNG có bất kỳ matcher nào khớp nhau.
- Object
- equalTo : test đối tượng bằng với một đối tượng sử dụng Object.equals.
- hasToString : test đối tượng bằng với một đối tượng sử dụng Object.toString.
- instanceOf, isCompatibleType :test kiểu dữ liệu (type).
- notNullValue, nullValue : test null hoặc Not null.
- sameInstance : test object identity
- Beans
- hasProperty : test một JavaBean có một property hay không.
- Collections
- array : kiểm tra các phần tử của mảng với một mảng các Matcher.
- hasEntry, hasKey, hasValue : test một map có chứa một entry, key hoặc value.
- hasItem, hasItems : test một collection có chứa một hay nhiều phần tử (element) hay không.
- hasItemInArray : test một array có chứa một element hay không.
- Number
- closeTo : test các giá trị dấu phẩy động gần với một giá trị đã cho.
- greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo – test một số có lớn hơn, lớn hơn hoặc bằng, nhỏ hơn, nhỏ hơn hoặc bằng.
- Text
- equalToIgnoringCase : test một chuỗi có bằng với một chuỗi đã cho không phân biệt hoa thường.
- equalToIgnoringWhiteSpace : test một chuỗi có bằng với một chuỗi đã cho bỏ qua sự khác biệt về khoảng trắng.
- containsString, endsWith, startsWith – test một chuỗi có chứa hoặc kết thúc hoặc bắt đầu bằng chuỗi đã cho.
- Sugar
- Hamcrest cố gắng làm cho các test dễ đọc nhất có thể. Nó giúp chúng ta có thể kết hợp nhiều Matcher lại với nhau trong một wrapper. Ví dụ các cách test sau đây đều tương đương:
assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));
- Cách test cuối cùng đơn giản chỉ sử dụng is(T value) , nó được overloaded để trả về kết quả tương đương với is(equalTo(value)).
Cài đặt Hamcrest Matchers trong Eclipse
Mặc định, khi chúng ta khai báo lưu viện JUnit 4.12 trong file pom.xml của project maven thì nó đã bao gồm thư viện hamcrest-core. Tuy nhiên, chúng ta nên include nó ra và khai báo một dependency đầy đủ của Hamcrest library, nó chứa nhiều phương thức hữu ích để test kiểu dữ liệu List.
pom.xml
<!-- https://mvnrepository.com/artifact/junit/junit --> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency> <!-- This will get hamcrest-core automatically --> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.3</version> <scope>test</scope> </dependency> </dependencies>
Đối với các phương thức static, chúng ta có thể thêm thư viện Hamcrest vào cửa sổ hỗ trợ gợi ý import trên editor của Eclipse bằng cách vào Menu Window –> Preferences –> chọn navigator bên trái Java/Editor/Content Assist/Favorites –> chọn button “New Type…” và thêm các thư viện static import còn thiếu.
Ví dụ sử dụng Hamcrest Matchers
Ví dụ sử dụng Core Matcher, Text Matcher, Logical Matcher, Object Matcher
package com.gpcoder.junit.hamcrest; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.anything; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.describedAs; import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.equalToIgnoringWhiteSpace; import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.core.IsNot.not; import java.util.Calendar; import java.util.Locale; import org.hamcrest.core.IsSame; import org.junit.Test; class Today { /** * Provide the day of the week of today's date. * * @return Integer representing today's day of the week, corresponding to static * fields defined in Calendar class. */ public int getTodayDayOfWeek() { return Calendar.getInstance(Locale.US).get(Calendar.DAY_OF_WEEK); } } public class JUnitMatchers { // Is method checks two values are equal or not. // If they are equal it returns true! @Test public void isMatcherTest() { assertThat("gpcoder", is("gpcoder")); assertThat(true, is(true)); assertThat(2019, is(2019)); } // IsNot method checks two values are equal or not. // If they are not equal it returns true! @Test public void isnotMatcherTest() { assertThat("gpcoder.com", is(not("GPCODER"))); } // IsEqual method checks given objects equality. @Test public void isEqualMatcherTest() { assertThat("gpcoder", equalTo("gpcoder")); assertThat("gpcoder", is(equalTo("gpcoder"))); } // IsNot method creates a matcher that wraps an existing matcher, but inverts // the logic by which it will match. @Test public void isNotMatcherTest() { assertThat("gpcoder", not(equalTo("Java"))); assertThat("gpcoder", is(not(equalTo("Java")))); } // EqualToIgnoringCase creates a matcher that matches if examined object is equals ignore case. @Test public void equalToIgnoringCaseTest() { assertThat("gpcoder", equalToIgnoringCase("GPCODER")); } // EqualToIgnoringCase creates a matcher that matches if examined object is equals ignore case. @Test public void equalToIgnoringWhiteSpaceTest() { assertThat("gpcoder", equalToIgnoringWhiteSpace("GP CODER")); } // IsNull creates a matcher that matches if examined object is null. @Test public void isNullMatcherTest() { assertThat(null, is(nullValue())); assertThat("gpcoder", is(notNullValue())); } // HasToString creates a matcher that matches if examined object is has To String. @Test public void hasToStringTest() { assertThat(4, hasToString("4")); assertThat(3.14, hasToString(containsString("."))); } // AllOf method creates a matcher that matches // if the examined object matches ALL of the specified matchers. @Test public void allOfMatcherTest() { assertThat("gpcoder.com", allOf(startsWith("gpcoder"), containsString("gp"), endsWith(".com"))); } // AnyOf method creates a matcher that matches // if the examined object matches ANY of the specified matchers. @Test public void anyOfMatcherTest() { assertThat("gpcoder", anyOf(startsWith("gpcoder"), containsString(".com"))); final Today instance = new Today(); final int todayDayOfWeek = instance.getTodayDayOfWeek(); assertThat(todayDayOfWeek, describedAs("Day of week is not in range", anyOf(is(Calendar.SUNDAY), is(Calendar.MONDAY), is(Calendar.TUESDAY), is(Calendar.WEDNESDAY), is(Calendar.THURSDAY), is(Calendar.FRIDAY), is(Calendar.SATURDAY)))); } // IsInstanceOf method creates a matcher that matches when the examined object // is an instance of the specified type, as determined by calling the // Class.isInstance(Object) method on that type, passing the the examined object. @Test public void isInstanceOfMatcherTest() { assertThat(new JUnitMatchers(), instanceOf(JUnitMatchers.class)); } // IsSame method creates a matcher that matches only when the // examined object is the same instance as the specified target object. @Test public void isSameMatcherTest() { String str1 = "gpcoder"; String str2 = "gpcoder"; assertThat(str1, IsSame.<String>sameInstance(str2)); } // IsAnything method is a matcher that always returns true. @Test public void isAnythingMatcherTest() { assertThat("gpcoder", is(anything())); assertThat(1, is(anything())); } // describedAs method adds a description to a Matcher // When test is failed, it will show error like that: // java.lang.AssertionError: Expected: Sunday is not Saturday. but: was "Sunday" @Test public void describedAsMatcherTest() { assertThat("Sunday", describedAs("Sunday is not Saturday.", is("Saturday"))); } }
Ví dụ sử dụng Number Matcher
package com.gpcoder.junit.hamcrest; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.comparesEqualTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertThat; import java.math.BigDecimal; import java.time.LocalDate; import org.junit.Test; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor class Person implements Comparable<Person> { private String name; private int age; @Override public int compareTo(Person o) { if (this.age == o.getAge()) return 0; if (this.age > o.getAge()) return 1; else return -1; } } public class NumberMatcherTest { @Test public void givenADouble_whenCloseTo_thenCorrect() { double actual = 1.3; double operand = 1; double error = 0.5; assertThat(actual, closeTo(operand, error)); } @Test public void givenADouble_whenNotCloseTo_thenCorrect() { double actual = 1.6; double operand = 1; double error = 0.5; assertThat(actual, not(closeTo(operand, error))); } @Test public void givenABigDecimal_whenCloseTo_thenCorrect() { BigDecimal actual = new BigDecimal("1.0003"); BigDecimal operand = new BigDecimal("1"); BigDecimal error = new BigDecimal("0.0005"); assertThat(actual, is(closeTo(operand, error))); } @Test public void givenABigDecimal_whenNotCloseTo_thenCorrect() { BigDecimal actual = new BigDecimal("1.0006"); BigDecimal operand = new BigDecimal("1"); BigDecimal error = new BigDecimal("0.0005"); assertThat(actual, is(not(closeTo(operand, error)))); } @Test public void given5_whenComparesEqualTo5_thenCorrect() { Integer five = 5; assertThat(five, comparesEqualTo(five)); } @Test public void given5_whenNotComparesEqualTo7_thenCorrect() { Integer seven = 7; Integer five = 5; assertThat(five, not(comparesEqualTo(seven))); } @Test public void given7_whenGreaterThan5_thenCorrect() { Integer seven = 7; Integer five = 5; assertThat(seven, is(greaterThan(five))); } @Test public void given7_whenGreaterThanOrEqualTo5_thenCorrect() { Integer seven = 7; Integer five = 5; assertThat(seven, is(greaterThanOrEqualTo(five))); } @Test public void given5_whenGreaterThanOrEqualTo5_thenCorrect() { Integer five = 5; assertThat(five, is(greaterThanOrEqualTo(five))); } @Test public void given3_whenLessThan5_thenCorrect() { Integer three = 3; Integer five = 5; assertThat(three, is(lessThan(five))); } @Test public void given3_whenLessThanOrEqualTo5_thenCorrect() { Integer three = 3; Integer five = 5; assertThat(three, is(lessThanOrEqualTo(five))); } @Test public void given5_whenLessThanOrEqualTo5_thenCorrect() { Integer five = 5; assertThat(five, is(lessThanOrEqualTo(five))); } @Test public void givenGpcoder_whenGreaterThanJavaGpcoder_thenCorrect() { String str1 = "Gpcoder"; String str2 = "JavaGpcoder"; assertThat(str1, is(greaterThan(str2))); } @Test public void givenJavaGpcoder_whenLessThanBenajmin_thenCorrect() { String str1 = "gpcoder"; String str2 = "gpcoder.com"; assertThat(str1, is(lessThan(str2))); } @Test public void givenToday_whenGreaterThanYesterday_thenCorrect() { LocalDate today = LocalDate.now(); LocalDate yesterday = today.minusDays(1); assertThat(today, is(greaterThan(yesterday))); } @Test public void givenToday_whenLessThanTomorrow_thenCorrect() { LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plusDays(1); assertThat(today, is(lessThan(tomorrow))); } @Test public void givenJavaGpcoder_whenOlderThanGpcoder_thenCorrect() { Person amanda = new Person("JavaGpcoder", 20); Person benjamin = new Person("Gpcoder", 18); assertThat(amanda, is(greaterThan(benjamin))); } @Test public void givenGpcoder_whenYoungerThanJavaGpcoder_thenCorrect() { Person amanda = new Person("JavaGpcoder", 20); Person benjamin = new Person("Gpcoder", 18); assertThat(benjamin, is(lessThan(amanda))); } }
Ví dụ sử dụng XML Matcher
package com.gpcoder.junit.hamcrest; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasXPath; import static org.junit.Assert.assertThat; import java.io.ByteArrayInputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.junit.Test; import org.w3c.dom.Document; public class XmlMatcherExample { @Test public void test_xml_path () throws Exception { String aListApartXML = "<tutorials>" + " <tutorial>" + " <title lang=\"en\">JUnit</title>" + " <author>gpcoder.com</author>" + " <year>2019</year>" + " </tutorial>" + "</tutorials>"; Document xml = parse(aListApartXML); assertThat(xml, hasXPath("/tutorials/tutorial/author")); assertThat(xml, hasXPath("/tutorials/tutorial/author", equalTo("gpcoder.com"))); } private static Document parse(String xml) throws Exception { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(false); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); return documentBuilder.parse(new ByteArrayInputStream(xml.getBytes())); } }
Ví dụ sử dụng Bean Matcher
package com.gpcoder.junit.hamcrest; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.samePropertyValuesAs; import static org.junit.Assert.assertThat; import org.junit.Test; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @Data @AllArgsConstructor @EqualsAndHashCode class Student { private int id; private String name; } public class BeanMatcherExample { @Test public void givenBean_whenHasValue_thenCorrect() { Student student = new Student(29, "gpcoder"); assertThat(student, hasProperty("gpcoder")); } @Test public void givenBean_whenHasCorrectValue_thenCorrect() { Student student = new Student(29, "gpcoder"); assertThat(student, hasProperty("name", equalTo("gpcoder"))); } @Test public void given2Beans_whenHavingSameValues_thenCorrect() { Student student1 = new Student(29, "gpcoder"); Student student2 = new Student(29, "gpcoder"); assertThat(student1, samePropertyValuesAs(student2)); } }
Ví dụ sử dụng Collection Matcher
package com.gpcoder.junit.hamcrest; import static org.hamcrest.CoreMatchers.everyItem; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; import static org.hamcrest.collection.IsArrayWithSize.arrayWithSize; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.hamcrest.number.OrderingComparison.lessThan; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.hamcrest.collection.IsEmptyCollection; import org.hamcrest.collection.IsMapContaining; import org.junit.Test; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; public class CollectionMatcherTest { @Test public void arrayTest() { Integer[] ints = new Integer[] { 2, 5, 1, 9 }; assertThat(ints, arrayWithSize(4)); assertThat(ints, arrayContainingInAnyOrder(2, 5, 1, 9)); } @Test public void testAssertMap() { Map<String, String> map = new HashMap<>(); map.put("j", "Java"); map.put("s", "Spring Boot"); map.put("w", "Web services"); Map<String, String> expected = new HashMap<>(); expected.put("j", "Java"); expected.put("s", "Spring Boot"); expected.put("w", "Web service"); // Test equal, ignore order assertThat(map, is(expected)); // Test size assertThat(map.size(), is(3)); // Test map entry assertThat(map, IsMapContaining.hasEntry("s", "Spring Boot")); assertThat(map, not(IsMapContaining.hasEntry("s", "SQL"))); // Test map key assertThat(map, IsMapContaining.hasKey("j")); // Test map value assertThat(map, IsMapContaining.hasValue("Java")); } @Test public void testAssertListString() { List<String> actual = Arrays.asList("Java", "Spring Boot", "Web services"); List<String> expected = Arrays.asList("Java", "Spring Boot", "Web services"); // Test equal. assertThat(actual, is(expected)); // If List has this value? assertThat(actual, hasItems("Java")); // Check List Size assertThat(actual, hasSize(3)); assertThat(actual.size(), is(3)); // Ensure Correct order assertThat(actual, contains("Java", "Spring Boot", "Web services")); // Can be any order assertThat(actual, containsInAnyOrder("Web services", "Spring Boot")); assertThat(actual, everyItem(greaterThan("A"))); // Check empty list assertThat(actual, not(IsEmptyCollection.empty())); assertThat(new ArrayList<>(), IsEmptyCollection.empty()); } @Test public void testAssertListInteger() { List<Integer> actual = Arrays.asList(1, 2, 3, 4, 5); List<Integer> expected = Arrays.asList(1, 2, 3, 4, 5); // Test equal. assertThat(actual, is(expected)); // Check List has this value assertThat(actual, hasItems(2)); // Check List Size assertThat(actual, hasSize(5)); assertThat(actual.size(), is(5)); // Ensure Correct order assertThat(actual, contains(1, 2, 3, 4, 5)); // Can be any order assertThat(actual, containsInAnyOrder(5, 4, 3, 2, 1)); // check empty list assertThat(actual, not(IsEmptyCollection.empty())); assertThat(new ArrayList<>(), IsEmptyCollection.empty()); // Test numeric comparisons assertThat(actual, everyItem(greaterThanOrEqualTo(1))); assertThat(actual, everyItem(lessThan(10))); } @Test public void testAssertListObject() { List<Fruit> list = Arrays.asList( new Fruit("Banana", 99), new Fruit("Apple", 20) ); // Test equals assertThat(list, hasItems( new Fruit("Banana", 99), new Fruit("Apple", 20) )); assertThat(list, containsInAnyOrder( new Fruit("Apple", 20), new Fruit("Banana", 99) )); // Test class property, and its value assertThat(list, containsInAnyOrder( hasProperty("name", is("Apple")), hasProperty("name", is("Banana")) )); } @Data @AllArgsConstructor @EqualsAndHashCode public class Fruit { private String name; private int qty; } }
Tài liệu tham khảo:
- http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html
- https://junit.org/junit4/javadoc/latest/org/hamcrest/CoreMatchers.html
- https://code.google.com/archive/p/hamcrest/wikis/Tutorial.wiki
- https://www.baeldung.com/hamcrest-number-matchers
- http://www.mkyong.com/unittest/junit-how-to-test-a-list/