Asserting properties on list elements with assertJ - java

I have a working hamcrest assertion:
assertThat(mylist, contains(
containsString("15"),
containsString("217")));
The intended behavior is:
mylist == asList("Abcd15", "217aB") => success
myList == asList("Abcd15", "218") => failure
How can I migrate this expression to assertJ. Of course there exist naive solutions, like asserting on the first and second value, like this:
assertThat(mylist.get(0)).contains("15");
assertThat(mylist.get(1)).contains("217");
But these are assertions on the list elements, not on the list. Trying asserts on the list restricts me to very generic functions. So maybe it could be only resolved with a custom assertion, something like the following would be fine:
assertThat(mylist).elements()
.next().contains("15")
.next().contains("217")
But before I write a custom assert, I would be interested in how others would solve this problem?
Edit: One additional non-functional requirement is, that the test should be easily extendible by additional contstraints. In Hamcrest it is quite easy to express additional constraints, e.g.
assertThat(mylist, contains(
emptyString(), //additional element
allOf(containsString("08"), containsString("15")), //extended constraint
containsString("217"))); // unchanged
Tests being dependent on the list index will have to be renumbered for this example, Tests using a custom condition will have to rewrite the complete condition (note that the constraints in allOf are not restricted to substring checks).

For this kind of assertions Hamcrest is superior to AssertJ, you can mimic Hamcrest with Conditions but you need to write them as there are none provided out of the box in AssertJ (assertJ philosphy is not to compete with Hamcrest on this aspect).
In the next AssertJ version (soon to be released!), you will be able to reuse Hamcrest Matcher to build AssertJ conditions, example:
Condition<String> containing123 = new HamcrestCondition<>(containsString("123"));
// assertions succeed
assertThat("abc123").is(containing123);
assertThat("def456").isNot(containing123);
As a final note, this suggestion ...
assertThat(mylist).elements()
.next().contains("15")
.next().contains("217")
... unfortunately can't work because of generics limitation, although you know that you have a List of String, Java generics are not powerful enough to choose a specific type (StringAssert) depending on another (String), this means you can only perform Object assertion on the elements but not String assertion.
-- edit --
Since 3.13.0 one can use asInstanceOf to get specific type assertions, this is useful if the declared type is Object but the runtime type is more specific.
Example:
// Given a String declared as an Object
Object value = "Once upon a time in the west";
// With asInstanceOf, we switch to specific String assertion by specifying the InstanceOfAssertFactory for String
assertThat(value).asInstanceOf(InstanceOfAssertFactories.STRING)
.startsWith("Once");`
see https://assertj.github.io/doc/#assertj-core-3.13.0-asInstanceOf

AssertJ v3.19.0 or newer: use satisfiesExactly.
AssertJ v3.19.0, released in 2021, has added a satisfiesExactly method.
So you can write:
assertThat(mylist)
.satisfiesExactly(item1 -> assertThat(item1).contains("15"),
item2 -> assertThat(item2).contains("217"));
You can add more assertions to individual elements if need be:
assertThat(mylist)
.satisfiesExactly(item1 -> assertThat(item1)
.contains("08")
.contains("15"),
item2 -> assertThat(item2).contains("217"));
In comparison to the technique that uses a next() chain, this one also checks the list size for you. As an added benefit, it lets you use whatever lambda parameter you like, so it’s easier to read and to keep track of which element you’re in.

You can use anyMatch
assertThat(mylist)
.anyMatch(item -> item.contains("15"))
.anyMatch(item -> item.contains("217"))
but unfortunately the failure message cannot tell you internals about the expectations
Expecting any elements of:
<["Abcd15", "218"]>
to match given predicate but none did.

The closest I've found is to write a "ContainsSubstring" condition, and a static method to create one, and use
assertThat(list).has(containsSubstring("15", atIndex(0)))
.has(containsSubstring("217", atIndex(1)));
But maybe you should simply write a loop:
List<String> list = ...;
List<String> expectedSubstrings = Arrays.asList("15", "217");
for (int i = 0; i < list.size(); i++) {
assertThat(list.get(i)).contains(expectedSubstrings.get(i));
}
Or to write a parameterized test, so that each element is tested on each substring by JUnit itself.

You can do the following:
List<String> list1 = Arrays.asList("Abcd15", "217aB");
List<String> list2 = Arrays.asList("Abcd15", "218");
Comparator<String> containingSubstring = (o1, o2) -> o1.contains(o2) ? 0 : 1;
assertThat(list1).usingElementComparator(containingSubstring).contains("15", "217"); // passes
assertThat(list2).usingElementComparator(containingSubstring).contains("15", "217"); // fails
The error it gives is:
java.lang.AssertionError:
Expecting:
<["Abcd15", "218"]>
to contain:
<["15", "217"]>
but could not find:
<["217"]>

In fact, you must implements your own Condition in assertj for checking the collection containing the substrings in order. for example:
assertThat(items).has(containsExactly(
stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new)
));
What's approach did I choose to meet your requirements? write a contract test case, and then implements the feature that the assertj doesn't given, here is my test case for the hamcrest contains(containsString(...)) adapt to assertj containsExactly as below:
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Collection;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
#RunWith(Parameterized.class)
public class MatchersTest {
private final SubstringExpectation expectation;
public MatchersTest(SubstringExpectation expectation) {
this.expectation = expectation;
}
#Parameters
public static List<SubstringExpectation> parameters() {
return asList(MatchersTest::hamcrest, MatchersTest::assertj);
}
private static void assertj(Collection<? extends String> items, String... subItems) {
Assertions.assertThat(items).has(containsExactly(stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new)));
}
private static Condition<String> containsSubstring(String substring) {
return new Condition<>(s -> s.contains(substring), "contains substring: \"%s\"", substring);
}
#SuppressWarnings("unchecked")
private static <C extends Condition<? super T>, T extends Iterable<? extends E>, E> C containsExactly(Condition<E>... conditions) {
return (C) new Condition<T>("contains exactly:" + stream(conditions).map(it -> it.toString()).collect(toList())) {
#Override
public boolean matches(T items) {
int size = 0;
for (E item : items) {
if (!matches(item, size++)) return false;
}
return size == conditions.length;
}
private boolean matches(E item, int i) {
return i < conditions.length && conditions[i].matches(item);
}
};
}
private static void hamcrest(Collection<? extends String> items, String... subItems) {
assertThat(items, contains(stream(subItems).map(Matchers::containsString).collect(toList())));
}
#Test
public void matchAll() {
expectation.checking(asList("foo", "bar"), "foo", "bar");
}
#Test
public void matchAllContainingSubSequence() {
expectation.checking(asList("foo", "bar"), "fo", "ba");
}
#Test
public void matchPartlyContainingSubSequence() {
try {
expectation.checking(asList("foo", "bar"), "fo");
fail();
} catch (AssertionError expected) {
assertThat(expected.getMessage(), containsString("\"bar\""));
}
}
#Test
public void matchAgainstWithManySubstrings() {
try {
expectation.checking(asList("foo", "bar"), "fo", "ba", "<many>");
fail();
} catch (AssertionError expected) {
assertThat(expected.getMessage(), containsString("<many>"));
}
}
private void fail() {
throw new IllegalStateException("should failed");
}
interface SubstringExpectation {
void checking(Collection<? extends String> items, String... subItems);
}
}
However, you down to use chained Conditions rather than the assertj fluent api, so I suggest you to try use the hamcrest instead. in other words, if you use this style in assertj you must write many Conditions or adapt hamcrest Matchers to assertj Condition.

Related

How to keep track of a String variable while changing it with Functions using Stream API?

I want to use Stream API to keep track of a variable while changing it with functions.
My code:
public String encoder(String texteClair) {
for (Crypteur crypteur : algo) {
texteClair = crypteur.encoder(texteClair);
}
return texteClair;
}
I have a list of classes that have methods and I want to put a variable inside all of them, like done in the code above.
It works perfectly, but I was wondering how it could be done with streams?
Could we use reduce()?
Use an AtomicReference, which is effectively final, but its wrapped value may change:
public String encoder(String texteClair) {
AtomicReference<String> ref = new AtomicReference<>(texteClair);
algo.stream().forEach(c -> ref.updateAndGet(c::encoder)); // credit Ole V.V
return ref.get();
}
Could we use reduce()?
I guess we could. But keep in mind that it's not the best case to use streams.
Because you've mentioned "classes" in plural, I assume that Crypteur is either an abstract class or an interface. As a general rule you should favor interfaces over abstract classes, so I'll assume the that Crypteur is an interface (if it's not, that's not a big issue) and it has at least one implementation similar to this :
public interface Encoder {
String encoder(String str);
}
public class Crypteur implements Encoder {
private UnaryOperator<String> operator;
public Crypteur(UnaryOperator<String> operator) {
this.operator = operator;
}
#Override
public String encoder(String str) {
return operator.apply(str);
}
}
Then you can utilize your encoders with stream like this:
public static void main(String[] args) {
List<Crypteur> algo =
List.of(new Crypteur(str -> str.replaceAll("\\p{Punct}|\\p{Space}", "")),
new Crypteur(str -> str.toUpperCase(Locale.ROOT)),
new Crypteur(str -> str.replace('A', 'W')));
String result = encode(algo, "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system");
System.out.println(result);
}
public static String encode(Collection<Crypteur> algo, String str) {
return algo.stream()
.reduce(str,
(String result, Crypteur encoder) -> encoder.encoder(result),
(result1, result2) -> { throw new UnsupportedOperationException(); });
}
Note that combiner, which is used in parallel to combine partial results, deliberately throws an exception to indicate that this task ins't parallelizable. All transformations must be applied sequentially, we can't, for instance, apply some encoders on the given string and then apply the rest of them separately on the given string and merge the two results - it's not possible.
Output
EVERYPIECEOFKNOWLEDGEMUSTHWVEWSINGLEUNWMBIGUOUSWUTHORITWTIVEREPRESENTWTIONWITHINWSYSTEM

How to compare list of string with enum string values without Map or switch case?

In Enum i defined string values like
public enum Rara{
MA_ON("MO"),
MA_MAN("MG", "PH"),
MA_IP("MG", "PH", "IC"),
BAR("RC");
Rara(String... s){
//What here?
}
public Rara getValue(List<Val> v){
//What here?
}
}
From front end i am getting data like
<val>MO<val>
<val>PH</val> <--- This shall return MA_MAN from Enum
or we get data like
<val>MO</val> <---- This shall return MA_ON
Is there any tweak to do inside enum itself without using Map or switchcase but only with value parameters
so that i supply say List and it should return appropriate Enum Value?
The following should work:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public enum Rara {
MAG_STRIPE_ONLY("MGST"),
MAG_STRIPE_MANUAL("MGST", "PHYS"),
MAG_STRIPE_MANUAL_CHIP("MGST", "PHYS", "CICC"),
BARCODE("BRCD");
Rara(String... codes) {
this.codes = List.of(codes);
}
private List<String> codes;
public static Optional<Rara> getValue(List<String> values){
return Arrays.stream(values())
.filter(rara -> rara.codes.stream().anyMatch(values::contains))
.findFirst();
}
}
You basically need to iterate over the list of codes for each Rara and check if they match any of the provided values. If they do you simply return the first one (Optional because you might find none). Without further requirements on what to do when to find multiple Rare matches or what to do if none is found, this is the best we can suggest to you.
Given that you want a full match between values and codes, you need to use another filter predicate as follows:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public enum Rara {
MAG_STRIPE_ONLY("MGST"),
MAG_STRIPE_MANUAL("MGST", "PHYS"),
MAG_STRIPE_MANUAL_CHIP("MGST", "PHYS", "CICC"),
BARCODE("BRCD");
Rara(String... codes) {
this.codes = List.of(codes);
}
private List<String> codes;
public static Optional<Rara> getValue(List<String> values){
return Arrays.stream(values())
.filter(rara -> rara.codes.containsAll(values))
.findFirst();
}
}
The code bellow shows that this is in fact working:
public class Main {
public static void main(String args[]){
System.out.println(Rara.getValue(Arrays.asList("MGST", "PHYS")));
// Optional[MAG_STRIPE_MANUAL]
System.out.println(Rara.getValue(Arrays.asList("MGST")));
// Optional[MAG_STRIPE_ONLY]
}
}
See this code run live at IdeOne.com.
Only way is to iterate trough Rara.value and compare lists.

"Implementing" base factory method in Java/Groovy lang

I'm working on a model factory that create business models from test data stores in Katalon Studio. It's been years since I seriously touched Java, especially to do any sort of generic programming.
How I'm trying to do it
I'll have some BaseModelFactory, defined to be:
import java.util.stream.Collectors
import com.kms.katalon.core.testdata.TestData
public class BaseModelFactory<T> {
public static List ModelsFrom(TestData testData) {
return testData.getAllData()
.stream()
.map { row -> this.ModelFromRow(row) }
.collect(Collectors.toList())
}
public static <T> T ModelFrom(TestData testData, int rowNum) {
return ModelFromRow(testData.getAllData().get(rowNum))
}
private static <T> T ModelFromRow(List<Object> row) {
return null
}
}
and then, whenever we have a data store that we want to create business models from, we simply create a derived factory that implements ModelsFrom. For example:
import java.text.SimpleDateFormat
import com.xxx.models.contract.ContractModel
public class ContractModelFactory extends BaseModelFactory<ContractModel> {
private static ContractModel ModelFromRow(List<Object> row) {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
return new ContractModel(
Integer.parseInt(row[0]),
row[1],
dateFormat.parse(row[2]),
dateFormat.parse(row[3]),
//...
)
}
}
OK, so what's the problem?
The problem is that, whenever I run a Test Case, with
WebUI.verifyNotEqual(
ContractModelFactory.ModelFrom(findTestData("whatever"), 1),
null
)
, where "whatever" points to a non-empty data file, it fails! When I go to debug it, I see that BaseModelFactory.ModelFromRow is getting hit, instead of the hiding static method ContractModelFactory.ModelFromRow !
How do I fix this?
DISCLAIMER: In case you didn't see it in the start of the paragraph, I am doing this in Katalon Studio environment.

Is there any Hamcrest Matcher for java.util.Optional?

I am looking for a Hamcrest Matcher to unit test methods that return a java.util.Optional type. Something like:
#Test
public void get__Null(){
Optional<Element> element = Element.get(null);
assertThat( sasi , isEmptyOptional());
}
#Test
public void get__GetCode(){
Optional<Element> element = Element.get(MI_CODE);
assertThat( sasi , isOptionalThatMatches(allOf(hasproperty("code", MI_CODE),
hasProperty("id", notNullValue())));
}
Is there any implementation available throw the Maven Repository?
Presently Java Hamcrest is using 1.6 version and is integrated with many projects that use older version of Java.
So the features related to Java 8 will be added in future versions that are Java 8 compatible. The solution proposed was to have an extension library that supports it, so that anyone who needs can use extension library.
I am the author of Hamcrest Optional and it is now available on Maven central.
Example: Checking if the Optional contains a string starting with some value
import static com.github.npathai.hamcrestopt.OptionalMatchers.hasValue;
import static org.hamcrest.Matchers.startsWith;
Optional<String> optional = Optional.of("dummy value");
assertThat(optional, hasValue(startsWith("dummy")));
The Hamcrest Optional from Narendra Pathai does great job indeed.
import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty;
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresent;
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAnd;
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
#Test
public void testOptionalValue() {
Optional<String> option = Optional.of("value");
assertTrue(option.isPresent()); // the old-fashioned, non-diagnosable assertion
assertThat(option, isPresent());
assertThat(option, isPresentAndIs("value"));
assertThat(option, isPresentAnd(startsWith("v")));
assertThat(option, isEmpty()); // fails
}
The last assertion above fails and produces nice diagnosable message:
java.lang.AssertionError:
Expected: is <Empty>
but: had value "value"
Available on Maven :
<dependency>
<groupId>com.github.npathai</groupId>
<artifactId>hamcrest-optional</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
For the moment I have the following information:
There is an issue and a feature proposal to support it with othe Java 8 types on hamcrest site.
One user created one and posted on his GitHub as an example. Still not on Maven but working on it.
Till this works its way into the Hamcrest, or if you can't add an external library. If you are okay with adding a class, the following does the work for an empty optional
class EmptyOptionalMatcher<T> extends BaseMatcher<Optional<T>> {
private Optional<T> optionalActual;
public EmptyOptionalMatcher() {
}
#Override
public boolean matches(Object item) {
optionalActual = (Optional<T>) item;
if (optionalActual == null) {
return false;
}
boolean empty = !optionalActual.isPresent();
return empty;
}
#Override
public void describeTo(Description description) {
description.appendText("optional is empty");
}
#Override
public void describeMismatch(Object item, Description description) {
if (optionalActual == null) {
description.appendText(" optional was NULL?");
} else {
description.appendText(" was: " + optionalActual.get());
}
}
}
Then have a matchers helper or common class with this static method that you can import and use:
public static <T> Matcher<? super Optional<T>> emptyOptional() {
return new EmptyOptionalMatcher<>();
}
Usage as:
assertThat(someOptional, is(emptyOptional()));
OR the negative test as
assertThat(someOptional, is(not(emptyOptional())));
If you only want to verify that an optional field of some object is present/not present, you can use the following idiom:
Expected object:
public class BusinessObject {
private Long id;
private String name;
private Optional<AnotherObject> anotherObject;
}
Hamcrest test would look like this:
assertThat("BusinessObject is not as expected", businessObject, allOf(
hasProperty("id", equalTo(1L)),
hasProperty("name", equalTo("Some title")),
hasProperty("anotherObject", hasProperty("present", equalTo(true)))
));

Mockito; verify method was called with list, ignore order of elements in list

I have a class (ClassA) that get the files in a directory. It scans the given directory for files matching a regex. For each matching file, it adds a File Object to a list.
Once the directory is processed, it passes the List of Files to another Class (ClassB) for processing
I am writing unit tests for ClassA, so am mocking ClassB using Mockito, and injecting it into ClassA.
I then want to verify in different scenarios the contents of the list that is passed to ClassB (ie my mock)
I've stripped back the code to the following
public class ClassA implements Runnable {
private final ClassB classB;
public ClassA(final ClassB classB) {
this.classB = classB;
}
public List<File> getFilesFromDirectories() {
final List<File> newFileList = new ArrayList<File>();
// ...
return newFileList;
}
public void run() {
final List<File> fileList = getFilesFromDirectories();
if (fileList.isEmpty()) {
//Log Message
} else {
classB.sendEvent(fileList);
}
}
}
The test class looks like this
#RunWith(MockitoJUnitRunner.class)
public class AppTest {
#Rule
public TemporaryFolder folder = new TemporaryFolder();
#Mock
private ClassB mockClassB;
private File testFileOne;
private File testFileTwo;
private File testFileThree;
#Before
public void setup() throws IOException {
testFileOne = folder.newFile("testFileA.txt");
testFileTwo = folder.newFile("testFileB.txt");
testFileThree = folder.newFile("testFileC.txt");
}
#Test
public void run_secondFileCollectorRun_shouldNotProcessSameFilesAgainBecauseofDotLastFile() throws Exception {
final ClassA objUndertest = new ClassA(mockClassB);
final List<File> expectedFileList = createSortedExpectedFileList(testFileOne, testFileTwo, testFileThree);
objUndertest.run();
verify(mockClassB).sendEvent(expectedFileList);
}
private List<File> createSortedExpectedFileList(final File... files) {
final List<File> expectedFileList = new ArrayList<File>();
for (final File file : files) {
expectedFileList.add(file);
}
Collections.sort(expectedFileList);
return expectedFileList;
}
}
The problem is that this test works perfectly fine on windows, but fails on Linux. The reason being that on windows, the order that ClassA list the files matches the expectedList, so the line
verify(mockClassB).sendEvent(expectedFileList);
is causing the problem expecetdFileList = {FileA, FileB, FileC} on Windows, whereas on Linux it will be {FileC, FileB, FileA}, so the verify fails.
The question is, how do I get around this in Mockito. Is there any way of saying, I expect this method to be be called with this parameter, but I don't care about the order of the contents of the list.
I do have a solution, I just don't like it, I would rather have a cleaner, easier to read solution.
I can use an ArgumentCaptor to get the actual value passed into the mock, then can sort it, and compare it to my expected values.
final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(mockClassB).method(argument.capture());
Collections.sort(expected);
final List<String> value = argument.getValue();
Collections.sort(value);
assertEquals(expecetdFileList, value);
As noted in another answer, if you don't care about the order, you might do best to change the interface so it doesn't care about the order.
If order matters in the code but not in a specific test, you can use the ArgumentCaptor as you did. It clutters the code a bit.
If this is something you might do in multiple tests, you might do better to use appropriate Mockito Matchers or Hamcrest Matchers, or roll your own (if you don't find one that fills the need). A hamcrest matcher might be best as it can be used in other contexts besides mockito.
For this example you could create a hamcrest matcher as follows:
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MyMatchers {
public static <T> Matcher<List<T>> sameAsSet(final List<T> expectedList) {
return new BaseMatcher<List<T>>(){
#Override
public boolean matches(Object o) {
List<T> actualList = Collections.EMPTY_LIST;
try {
actualList = (List<T>) o;
}
catch (ClassCastException e) {
return false;
}
Set<T> expectedSet = new HashSet<T>(expectedList);
Set<T> actualSet = new HashSet<T>(actualList);
return actualSet.equals(expectedSet);
}
#Override
public void describeTo(Description description) {
description.appendText("should contain all and only elements of ").appendValue(expectedList);
}
};
}
}
And then the verify code becomes:
verify(mockClassB).sendEvent(argThat(MyMatchers.sameAsSet(expectedFileList)));
If you instead created a mockito matcher, you wouldn't need the argThat, which basically wraps a hamcrest matcher in a mockito matcher.
This moves the logic of sorting or converting to set out of your test and makes it reusable.
An ArgumentCaptor probably is the best way to do what you want.
However, it seems that you don’t actually care about the order of the files in the List. Therefore, have you considered changing ClassB so that it takes an unordered collection (like a Set) instead?
A one-liner using argThat which compares the two lists as sets:
verify(mock).method(argThat(list -> new HashSet<>(expected).equals(new HashSet<>(list))));
You can use an ArgumentCaptor and then Hamcrest's Matchers.containsInAnyOrder() for assertion like this:
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(mockClassB).method(argument.capture());
List<String> value = argument.getValue();
assertThat(value, containsInAnyOrder("expected", "values");

Categories