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

Java 网站的JUnit测试包含一个字符串或(独占)其他字符串,java,testing,junit,hamcrest,Java,Testing,Junit,Hamcrest,在spring mvc项目中,我对索引/主页的内容进行了测试: @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class HomePageTest { @Autowired private MockMvc mockMvc; @Test public void shouldContainStrings() throws Exception { t

在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"))));
....
但这也不起作用(如果两个字符串都出现在页面中,则不会给出错误)。

只要方法接受Hamcrest matcher,我在这里看到两个选项:

  • 要么自己实现类似XOR的matcher(您可以将此答案用作参考)
  • …或使用复杂条件,如“其中任何一个,但不能同时使用两个”

    匹配器匹配器=
    全部(
    是(containsString(“登录”)。或(containsString(“注销”)),
    是(不是(allOf(containsString(“登录”),containsString(“注销”));
    断言(“_Login”,matcher);//好啊
    断言(“_Logout”,matcher);//好啊
    断言(“_Login_Logout_”,matcher);//失败
    断言(“_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu失败
    

  • 就我个人而言,我更喜欢使用第二种选择。

    当我找不到合适的时候,我不得不亲自写信给custom matcher

    @Test
    public void containsOneOfTwoSubStringsExclusive() {
        assertTrue((mainString.contains(substring1) && !mainString.contains(substring2)) || 
            (!mainString.contains(substring1) && mainString.contains(substring2)))
    }
    
    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 );
        }
    
    }
    
    import java.util.function.BiConsumer;
    导入javax.annotation.Nonnull;
    导入org.hamcrest.BaseMatcher;
    导入org.hamcrest.Description;
    导入org.hamcrest.Matcher;
    导入org.hamcrest.core.CombinableMatcher;
    /**
    *类似于{@link CombinableMatcher.combinableethermatcher},但仅当只有一个给定的
    *匹配器{@link Matcher#匹配(对象)}。
    *
    *@作者bugorskia
    */
    公共类EitherXorMatcher扩展BaseMatcher{
    //_**字段**_//
    @非空
    私人最终配对者<?超级T>配对者;
    @非空
    私人最终匹配者<?超级T>B匹配者;
    //_**内部阶级**_//
    /**
    *这仅适用于builder模式/fluent界面。
    */
    公共静态最终类EitherXorMatcherBuilder{
    //_**字段**_//
    @非空
    
    private final matcher我认为您应该有两种测试方法:一种是因为您没有登录而期望“登录”,另一种是因为您没有登录而期望“注销”。@davidxxx:这一点很好。但让我们假设它们是不同的字符串(而不是“登录/注销”)。是否有方法匹配此XOR,或者我必须编写自己的匹配程序?
    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 );
        }
    
    }