JUnit test for website contains one string or (exclusive) other string

2k views Asked by At

In a spring-mvc project I have a test for the content of the index/homepage:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HomePageTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldContainStrings() throws Exception {
        this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
            .andExpect(content().string(containsString("Hello World")));
    }
}

This test works fine so far. But now I would like to test for the occurrence of the strings "Login" or (excl) "Logout", i.e. I want to test whether exactly one (not zero and not both) of these two strings appears in the content. How can I match this or condition?

I tried

...
.andExpect(content().string(
      either(containsString("Login")).or(containsString("Logout"))));
....

But this does not work either (does not give an error if both strings appear in the page).

3

There are 3 answers

0
bsiamionau On BEST ANSWER

As long as string() method accepts Hamcrest matcher, I see two options here:

  1. Either implement XOR-like matcher yourself (you can use this answer as reference https://stackoverflow.com/a/29610402/1782379)...
  2. ...or use complex condition like "any of them but not both"

    Matcher<String> matcher =
            allOf(
                    is(either(containsString("Login")).or(containsString("Logout"))),
                    is(not(allOf(containsString("Login"), containsString("Logout")))));
    assertThat("_Login_", matcher); // OK
    assertThat("_Logout_", matcher); // OK
    assertThat("_Login_Logout_", matcher); // FAIL
    assertThat("__", matcher); // FAIL
    

Personally I prefer to use second option.

0
Mat Jones On
@Test
public void containsOneOfTwoSubStringsExclusive() {
    assertTrue((mainString.contains(substring1) && !mainString.contains(substring2)) || 
        (!mainString.contains(substring1) && mainString.contains(substring2)))
}
0
Sled On

I just had to write to custom matcher for this myself when I could not find a suitable one.

import java.util.function.BiConsumer;

import javax.annotation.Nonnull;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.CombinableMatcher;

/**
 * Similar to the {@link CombinableMatcher.CombinableEitherMatcher} but only passes if <em>only one</em> of the given
 * matchers {@link Matcher#matches(Object)}.
 *
 * @author bugorskia
 */
public class EitherXorMatcher<T> extends BaseMatcher<T>{



    //_ **FIELDS** _//


    @Nonnull
    private final Matcher< ? super T > aMatcher;

    @Nonnull
    private final Matcher< ? super T > bMatcher;



    //_ **INNER CLASS**_//


    /**
     * This is just for the builder pattern/fluent interface.
     */
    public static final class EitherXorMatcherBuilder<T>{



        //_ **FIELDS** _//


        @Nonnull
        private final Matcher<? super T> aMatcher;



        //_ **CONSTRUCTOR** _//


        private EitherXorMatcherBuilder( @Nonnull final Matcher<? super T> aMatcher ){
            this.aMatcher = aMatcher;
        }



        //_ **API METHODS** _//


        @Nonnull
        public Matcher<T> xor( @Nonnull final Matcher<? super T> anotherMatcher ){
            return new EitherXorMatcher<>( aMatcher, anotherMatcher );
        }

    }



    //_ **CONSTRUCTOR** _//


    private EitherXorMatcher( @Nonnull final Matcher< ? super T > aMatcher, @Nonnull final Matcher< ? super T > bMatcher ){
        this.aMatcher = aMatcher;
        this.bMatcher = bMatcher;
    }


    @Nonnull
    public static <T> EitherXorMatcherBuilder<T> exclusivelyEither( final Matcher<? super T> aMatcher ){
        return new EitherXorMatcherBuilder<>( aMatcher );
    }


    @Nonnull
    public static <T> Matcher<? super T> exclusivelyEither( @Nonnull final Matcher<? super T> aMatcher, @Nonnull final Matcher<? super T> bMatcher ){
        return new EitherXorMatcher<>( aMatcher, bMatcher );
    }


    @Nonnull @Deprecated
    public static <T> EitherXorMatcherBuilder<T> either( final Matcher<? super T> aMatcher ){
        return exclusivelyEither( aMatcher );
    }



    //_ **API METHODS** _//


    @Override
    public boolean matches( final Object item ){
        final boolean aMatches = aMatcher.matches( item );
        final boolean bMatches = bMatcher.matches( item );

        return xor( aMatches, bMatches );
    }


    @Override
    public void describeTo( final Description description ){
        description.appendText( "Either { " );
        aMatcher.describeTo( description );
        description.appendText( " } xor { " );
        bMatcher.describeTo( description );
        description.appendText( " } " );
    }


    @Override
    public void describeMismatch( final Object item, final Description description ){
        final boolean aMatches = aMatcher.matches( item );
        final boolean bMatches = bMatcher.matches( item );
        assert !xor( aMatches, bMatches ): "Should not have gotten called!";
        assert aMatches == bMatches: "This is implied, and more of a developer comment than a runtime check.";

        final BiConsumer<Matcher<? super T>,Description> describer;
        final String startWord, joinWord;
        if( aMatches ){
            startWord = "Both";
            joinWord = "and";
            describer = Matcher::describeTo;
        }else{
            startWord = "Neither";
            joinWord = "nor";
            describer = ( m, d ) -> m.describeMismatch( item, description );
        }

        description.appendText( startWord ).appendText( " { " );
        describer.accept( aMatcher, description );
        description.appendText( " } " ).appendText( joinWord ).appendText( " { " );
        describer.accept( bMatcher, description );
        description.appendText( " } " ).appendText( " matched instead of exactly one." );
    }



    //_ **HELPER METHODS** _//


    private static boolean xor( final boolean aMatches, final boolean bMatches ){
        // xor :: one or the other but not both
        return ( aMatches || bMatches )  &&  ! ( aMatches && bMatches );
    }

}