Reputation: 6568
I'm using Junit 4 w/ Selenium WebDriver for some automated testing. I have a test that records some values on a page, clicks a button on the page to load the next page and then compares the recorded value to make sure they changed.
The problem is it's a PAIR of values and only one of them has to change, not both.
For testing it I've currently implemented the following code:
boolean orderNumberChanged = true;
try
{
assertThat(orderNumber,
is(not(getValueForElement(By.name("updateForm:j_id35")))));
}
catch (AssertionError ae) { orderNumberChanged = false; }
try
{
assertThat(orderDate,
is(not(getValueForElement(By.name("updateForm:j_id37")))));
}
catch (AssertionError ae)
{
if ( !orderNumberChanged )
{ fail("OrderNumber and OrderDate didn't change."); }
}
While it should work perfectly fine it just looks ugly to me and it seams like there should be some way to assert something like this.
For the record getValueForElement()
is a method I wrote to wrap
driver.findElement(locator).getAttribute("value");
Upvotes: 0
Views: 3337
Reputation: 1460
The approved answer is complete but I think it is outdated and you need to code a lot. for anyone that is looking for a more simpler solution, you can achieve the same functionality as below:
1)
assertThat(actual).isIn(expected1, expected2);
assertThat(actual, anyOf(is(expected1), is(expected2)));
assertThat(actual, isOneOf(expected1, expected2));
String expectedTitles[] = {"expected1","expected2"};
List<String> expectedTitlesList = Arrays.asList(expectedTitles);
assertTrue(expectedTitlesList.contains((actual)));
Upvotes: 1
Reputation: 6568
The answer/comment suggesting assertTrue(orderNumber.equals(...) || orderDate.equals(...));
is indeed the simplest and most direct solution. However, I also wanted better error messages than just AssertionError
In order to do that was not trivial. I'm working with Selenium WebDriver so the actual data being checked belongs to a WebDriver
object.
I found my answer thanks to this: http://www.planetgeek.ch/2012/03/07/create-your-own-matcher/ blog post.
It turns out that Hamcrest has a CombinableMatcher
class and a CombinableEitherMatcher
I ended up copying CombinableEitherMatcher
's logic to create my own class Neither
public class Neither<X>
{
/**
* Creates a matcher that matches when neither of the specified matchers
* match the examined object.
* <p/>
* For example:
* <pre>
* assertThat("fan", neither(containsString("a")).nor(containsString("b")))
* </pre>
*/
public static <LHS> Neither<LHS> neither(Matcher<LHS> matcher)
{ return new Neither<LHS>(matcher); }
private final Matcher<X> first;
public Neither(Matcher<X> matcher) { this.first = not(matcher); }
public CombinableMatcher<X> nor(Matcher<X> other)
{ return new CombinableMatcher<X>(first).or(not(other)); }
/**
* Helper class to do the heavy lifting and provide a usable error
* from: http://www.planetgeek.ch/2012/03/07/create-your-own-matcher/
*/
private class WebElementCombinableMatcher extends BaseMatcher<WebElement>
{
private final List<Matcher<WebElement>> matchers = new ArrayList<Matcher<WebElement>>();
private final List<Matcher<WebElement>> failed = new ArrayList<Matcher<WebElement>>();
private WebElementCombinableMatcher(final Matcher matcher)
{ matchers.add(matcher); }
public WebElementCombinableMatcher and(final Matcher matcher)
{
matchers.add(matcher);
return this;
}
@Override
public boolean matches(final Object item)
{
for (final Matcher<WebElement> matcher : matchers)
{
if (!matcher.matches(item))
{
failed.add(matcher);
return false;
}
}
return true;
}
@Override
public void describeTo(final Description description)
{ description.appendList("(", " " + "and" + " ", ")", matchers); }
@Override
public void describeMismatch(final Object item, final Description description)
{
for (final Matcher<WebElement> matcher : failed)
{
description.appendDescriptionOf(matcher).appendText(" but ");
matcher.describeMismatch(item, description);
}
}
}
}
which lets me call
assertThat(driver, niether(matcher(x)).nor(matcher(y));
Since I'm trying to compare specific WebElements obtainable from driver I had to create matchers that I could use inside of niether/nor
For Example:
public class WebElementValueMatcher extends FeatureMatcher<WebDriver, String>
{
public static Matcher<WebDriver> elementHasValue(final By locator,
String elementValue)
{ return new WebElementValueMatcher(equalTo(elementValue), locator); }
public static Matcher<WebDriver> elementHasValue(final By locator,
Matcher<String> matcher)
{ return new WebElementValueMatcher(matcher, locator); }
By locator;
public WebElementValueMatcher(Matcher<String> subMatcher, By locator)
{
super(subMatcher, locator.toString(), locator.toString());
this.locator = locator;
}
public WebElementValueMatcher(Matcher<String> subMatcher,
String featureDescription, String featureName)
{ super(subMatcher, featureDescription, featureName); }
@Override
protected String featureValueOf(WebDriver actual)
{ return actual.findElement(locator).getAttribute("value"); }
}
I have another very similar one that calls actual.findElement(locator).getText();
in featureValueOf
However now I can simply call
assertThat(driver,
niether(elementHasValue(By.id("foo"),"foo"))
.nor(elementHasValue(By.id("bar"),"bar")));`
and get clean syntax in my tests and a usable error message.
Upvotes: 1
Reputation: 20520
You can't do this in a hamcrest style without severe contortions that make it much less readable than falling back to assertTrue
.
Just do
assertTrue(orderNumber.equals(...) || orderDate.equals(...));
The new assertThat
requires matching against a single object, so you'd have to put them into an array or something in order to get it through.
Upvotes: 1