/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.test.matcher;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.SelfDescribing;
import org.springframework.cloud.stream.test.binder.TestSupportBinder;
import org.springframework.integration.util.Function;
import org.springframework.messaging.Message;
/**
* A Hamcrest Matcher meant to be used in conjunction with {@link TestSupportBinder}.
*
* <p>Expected usage is of the form (with appropriate static imports):
* <pre>
* public class TransformProcessorApplicationTests {
*
* {@literal @}Autowired
* {@literal @}Bindings(TransformProcessor.class)
* private Processor processor;
*
* {@literal @}Autowired
* private MessageCollectorImpl messageCollector;
*
*
* {@literal @}Test
* public void testUsingExpression() {
* processor.input().send(new GenericMessage{@literal <}Object>("hello"));
* assertThat(messageCollector.forChannel(processor.output()), receivesPayloadThat(is("hellofoo")).within(10));
* }
*
* }</pre>
* </p>
*
* @author Eric Bottard
*/
public class MessageQueueMatcher<T> extends BaseMatcher<BlockingQueue<Message<?>>> {
private final Matcher<T> delegate;
private final long timeout;
private Extractor<Message<?>, T> extractor;
private Map<BlockingQueue<Message<?>>, T> actuallyReceived = new HashMap<>();
private final TimeUnit unit;
public MessageQueueMatcher(Matcher<T> delegate, long timeout, TimeUnit unit, Extractor<Message<?>, T> extractor) {
this.delegate = delegate;
this.timeout = timeout;
this.unit = (unit != null ? unit : TimeUnit.SECONDS);
this.extractor = extractor;
}
@Override
public boolean matches(Object item) {
@SuppressWarnings("unchecked")
BlockingQueue<Message<?>> queue = (BlockingQueue<Message<?>>) item;
Message<?> received = null;
try {
if (timeout > 0) {
received = queue.poll(timeout, unit);
}
else if (timeout == 0) {
received = queue.poll();
}
else {
received = queue.take();
}
}
catch (InterruptedException e) {
return false;
}
T unwrapped = extractor.apply(received);
actuallyReceived.put(queue, unwrapped);
return delegate.matches(unwrapped);
}
@Override
public void describeMismatch(Object item, Description description) {
@SuppressWarnings("unchecked")
BlockingQueue<Message<?>> queue = (BlockingQueue<Message<?>>) item;
T value = actuallyReceived.get(queue);
if (value != null) {
description.appendText("received: ").appendValue(value);
}
else {
description.appendText("timed out after " + timeout + " " + unit.name().toLowerCase());
}
}
public MessageQueueMatcher<T> within(long timeout, TimeUnit unit) {
return new MessageQueueMatcher<>(this.delegate, timeout, unit, this.extractor);
}
public MessageQueueMatcher<T> immediately() {
return new MessageQueueMatcher<>(this.delegate, 0, null, this.extractor);
}
public MessageQueueMatcher<T> indefinitely() {
return new MessageQueueMatcher<>(this.delegate, -1, null, this.extractor);
}
@Override
public void describeTo(Description description) {
description.appendText("Channel to receive ").appendDescriptionOf(extractor).appendDescriptionOf(delegate);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static <P> MessageQueueMatcher<P> receivesMessageThat(Matcher<Message<P>> messageMatcher) {
return new MessageQueueMatcher(messageMatcher, 5, TimeUnit.SECONDS, new Extractor<Message<P>, Message<P>>("a message that ") {
@Override
public Message<P> apply(Message<P> m) {
return m;
}
});
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static <P> MessageQueueMatcher<P> receivesPayloadThat(Matcher<P> payloadMatcher) {
return new MessageQueueMatcher(payloadMatcher, 5, TimeUnit.SECONDS, new Extractor<Message<P>, P>("a message whose payload ") {
@Override
public P apply(Message<P> m) {
return m.getPayload();
}
});
}
/**
* A transformation to be applied to a received message before asserting, <i>e.g.</i> to only inspect the payload.
*/
public static abstract class Extractor<R, T> implements Function<R, T>, SelfDescribing {
private final String behaviorDescription;
protected Extractor(String behaviorDescription) {
this.behaviorDescription = behaviorDescription;
}
@Override
public void describeTo(Description description) {
description.appendText(behaviorDescription);
}
}
}