网站的JUnit测试包含一个字符串或(独占)其他字符串

时间:2017-01-03 19:57:26

标签: java testing junit hamcrest

在spring-mvc项目中,我对索引/主页的内容进行了测试:

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

此测试到目前为止工作正常。但是现在我想测试字符串“Login”或(excl)“Logout”的出现,即我想测试这两个字符串中是否只有一个(不是零而不是两个)出现在内容中。我该如何匹配这个或条件?

我试过

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

但这也不起作用(如果页面中出现两个字符串,则不会出错)。

3 个答案:

答案 0 :(得分:1)

只要string()方法接受Hamcrest匹配器,我就会看到两个选项:

  1. 自己实现类似XOR的匹配器(您可以将此答案用作参考https://stackoverflow.com/a/29610402/1782379)...
  2. ...或使用复杂的条件,例如"其中任何一个但不是两个"

    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
    
  3. 我个人更喜欢使用第二种选择。

答案 1 :(得分:0)

@Test
public void containsOneOfTwoSubStringsExclusive() {
    assertTrue((mainString.contains(substring1) && !mainString.contains(substring2)) || 
        (!mainString.contains(substring1) && mainString.contains(substring2)))
}

答案 2 :(得分:0)

当我找不到合适的匹配器时,我必须自己写自定义匹配器。

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

}