Search code examples
javajunitselenium-webdriver

How can I assert that assertion A or assertion B passes with Junit 4?


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");


Solution

  • 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.