001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.web.reactive.server; 018 019import java.nio.charset.StandardCharsets; 020import java.util.Map; 021import java.util.Optional; 022import java.util.function.Consumer; 023 024import javax.xml.xpath.XPathExpressionException; 025 026import org.hamcrest.Matcher; 027 028import org.springframework.http.HttpHeaders; 029import org.springframework.lang.Nullable; 030import org.springframework.test.util.XpathExpectationsHelper; 031import org.springframework.util.Assert; 032import org.springframework.util.MimeType; 033 034/** 035 * XPath assertions for the {@link WebTestClient}. 036 * 037 * @author Eric Deandrea 038 * @author Rossen Stoyanchev 039 * @since 5.1 040 */ 041public class XpathAssertions { 042 043 private final WebTestClient.BodyContentSpec bodySpec; 044 045 private final XpathExpectationsHelper xpathHelper; 046 047 048 XpathAssertions(WebTestClient.BodyContentSpec spec, 049 String expression, @Nullable Map<String, String> namespaces, Object... args) { 050 051 this.bodySpec = spec; 052 this.xpathHelper = initXpathHelper(expression, namespaces, args); 053 } 054 055 private static XpathExpectationsHelper initXpathHelper( 056 String expression, @Nullable Map<String, String> namespaces, Object[] args) { 057 058 try { 059 return new XpathExpectationsHelper(expression, namespaces, args); 060 } 061 catch (XPathExpressionException ex) { 062 throw new AssertionError("XML parsing error", ex); 063 } 064 } 065 066 067 /** 068 * Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, String)}. 069 */ 070 public WebTestClient.BodyContentSpec isEqualTo(String expectedValue) { 071 return assertWith(() -> this.xpathHelper.assertString(getContent(), getCharset(), expectedValue)); 072 } 073 074 /** 075 * Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Double)}. 076 */ 077 public WebTestClient.BodyContentSpec isEqualTo(Double expectedValue) { 078 return assertWith(() -> this.xpathHelper.assertNumber(getContent(), getCharset(), expectedValue)); 079 } 080 081 /** 082 * Delegates to {@link XpathExpectationsHelper#assertBoolean(byte[], String, boolean)}. 083 */ 084 public WebTestClient.BodyContentSpec isEqualTo(boolean expectedValue) { 085 return assertWith(() -> this.xpathHelper.assertBoolean(getContent(), getCharset(), expectedValue)); 086 } 087 088 /** 089 * Delegates to {@link XpathExpectationsHelper#exists(byte[], String)}. 090 */ 091 public WebTestClient.BodyContentSpec exists() { 092 return assertWith(() -> this.xpathHelper.exists(getContent(), getCharset())); 093 } 094 095 /** 096 * Delegates to {@link XpathExpectationsHelper#doesNotExist(byte[], String)}. 097 */ 098 public WebTestClient.BodyContentSpec doesNotExist() { 099 return assertWith(() -> this.xpathHelper.doesNotExist(getContent(), getCharset())); 100 } 101 102 /** 103 * Delegates to {@link XpathExpectationsHelper#assertNodeCount(byte[], String, int)}. 104 */ 105 public WebTestClient.BodyContentSpec nodeCount(int expectedCount) { 106 return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), expectedCount)); 107 } 108 109 /** 110 * Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, Matcher)}. 111 * @since 5.1 112 */ 113 public WebTestClient.BodyContentSpec string(Matcher<? super String> matcher){ 114 return assertWith(() -> this.xpathHelper.assertString(getContent(), getCharset(), matcher)); 115 } 116 117 /** 118 * Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Matcher)}. 119 * @since 5.1 120 */ 121 public WebTestClient.BodyContentSpec number(Matcher<? super Double> matcher){ 122 return assertWith(() -> this.xpathHelper.assertNumber(getContent(), getCharset(), matcher)); 123 } 124 125 /** 126 * Delegates to {@link XpathExpectationsHelper#assertNodeCount(byte[], String, Matcher)}. 127 * @since 5.1 128 */ 129 public WebTestClient.BodyContentSpec nodeCount(Matcher<Integer> matcher){ 130 return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), matcher)); 131 } 132 133 /** 134 * Consume the result of the XPath evaluation as a String. 135 * @since 5.1 136 */ 137 public WebTestClient.BodyContentSpec string(Consumer<String> consumer){ 138 return assertWith(() -> { 139 String value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), String.class); 140 consumer.accept(value); 141 }); 142 } 143 144 /** 145 * Consume the result of the XPath evaluation as a Double. 146 * @since 5.1 147 */ 148 public WebTestClient.BodyContentSpec number(Consumer<Double> consumer){ 149 return assertWith(() -> { 150 Double value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), Double.class); 151 consumer.accept(value); 152 }); 153 } 154 155 /** 156 * Consume the count of nodes as result of the XPath evaluation. 157 * @since 5.1 158 */ 159 public WebTestClient.BodyContentSpec nodeCount(Consumer<Integer> consumer){ 160 return assertWith(() -> { 161 Integer value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), Integer.class); 162 consumer.accept(value); 163 }); 164 } 165 166 private WebTestClient.BodyContentSpec assertWith(CheckedExceptionTask task) { 167 try { 168 task.run(); 169 } 170 catch (Exception ex) { 171 throw new AssertionError("XML parsing error", ex); 172 } 173 return this.bodySpec; 174 } 175 176 private byte[] getContent() { 177 byte[] body = this.bodySpec.returnResult().getResponseBody(); 178 Assert.notNull(body, "Expected body content"); 179 return body; 180 } 181 182 private String getCharset() { 183 return Optional.of(this.bodySpec.returnResult()) 184 .map(EntityExchangeResult::getResponseHeaders) 185 .map(HttpHeaders::getContentType) 186 .map(MimeType::getCharset) 187 .orElse(StandardCharsets.UTF_8) 188 .name(); 189 } 190 191 192 @Override 193 public boolean equals(Object obj) { 194 throw new AssertionError("Object#equals is disabled " + 195 "to avoid being used in error instead of XPathAssertions#isEqualTo(String)."); 196 } 197 198 @Override 199 public int hashCode() { 200 return super.hashCode(); 201 } 202 203 204 /** 205 * Lets us be able to use lambda expressions that could throw checked exceptions, since 206 * {@link XpathExpectationsHelper} throws {@link Exception} on its methods. 207 */ 208 private interface CheckedExceptionTask { 209 210 void run() throws Exception; 211 212 } 213}