1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-12-08 12:01:09 +01:00

Introduce test fixtures

This also removes the powermock dependency. Although powermock is a
fine library, it currently prevents dropping Junit4. And since we only
use the Whitebox API of powermock, this simply replaced powermock's
Whitebox with our own.
This commit is contained in:
Florian Schmaus 2020-04-11 21:59:21 +02:00
parent 4a99f7252c
commit b5f9d4d7a3
51 changed files with 123 additions and 80 deletions

View file

@ -0,0 +1,138 @@
/**
*
* Copyright 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.jivesoftware.util;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.StanzaCollector;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaFactory;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* A collection of utility methods to create mocked XMPP connections.
*
* @author Henning Staib
*/
public class ConnectionUtils {
/**
* Creates a mocked XMPP connection that stores every stanza that is send over this
* connection in the given protocol instance and returns the predefined answer packets
* form the protocol instance.
* <p>
* This mocked connection can used to collect packets that require a reply using a
* StanzaCollector.
*
* <pre>
* <code>
* StanzaCollector collector = connection.createStanzaCollector(new PacketFilter());
* connection.sendStanza(packet);
* Stanza reply = collector.nextResult();
* </code>
* </pre>
*
* @param protocol protocol helper containing answer packets
* @param initiatorJID the user associated to the XMPP connection
* @return a mocked XMPP connection
* @throws SmackException if Smack detected an exceptional situation.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws InterruptedException if the calling thread was interrupted.
*/
public static XMPPConnection createMockedConnection(final Protocol protocol,
EntityFullJid initiatorJID) throws SmackException, XMPPErrorException, InterruptedException {
DomainBareJid xmppServer = initiatorJID.asDomainBareJid();
// mock XMPP connection
XMPPConnection connection = mock(XMPPConnection.class);
when(connection.getUser()).thenReturn(initiatorJID);
when(connection.getXMPPServiceDomain()).thenReturn(xmppServer);
final StanzaFactory stanzaFactory = new StanzaFactory(new StandardStanzaIdSource());
when(connection.getStanzaFactory()).thenReturn(stanzaFactory);
// mock packet collector
final StanzaCollector collector = mock(StanzaCollector.class);
when(connection.createStanzaCollector(isA(StanzaFilter.class))).thenReturn(
collector);
Answer<StanzaCollector> collectorAndSend = new Answer<StanzaCollector>() {
@Override
public StanzaCollector answer(InvocationOnMock invocation) throws Throwable {
Stanza packet = (Stanza) invocation.getArguments()[0];
protocol.getRequests().add(packet);
return collector;
}
};
when(connection.createStanzaCollectorAndSend(isA(IQ.class))).thenAnswer(collectorAndSend);
// mock send method
Answer<Object> addIncoming = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
protocol.getRequests().add((Stanza) invocation.getArguments()[0]);
return null;
}
};
doAnswer(addIncoming).when(connection).sendStanza(isA(Stanza.class));
// mock receive methods
Answer<Stanza> answer = new Answer<Stanza>() {
@Override
public Stanza answer(InvocationOnMock invocation) throws Throwable {
return protocol.getResponses().poll();
}
};
when(collector.nextResult(anyInt())).thenAnswer(answer);
when(collector.nextResult()).thenAnswer(answer);
Answer<Stanza> answerOrThrow = new Answer<Stanza>() {
@Override
public Stanza answer(InvocationOnMock invocation) throws Throwable {
Stanza packet = protocol.getResponses().poll();
if (packet == null) return packet;
XMPPErrorException.ifHasErrorThenThrow(packet);
return packet;
}
};
when(collector.nextResultOrThrow()).thenAnswer(answerOrThrow);
when(collector.nextResultOrThrow(anyLong())).thenAnswer(answerOrThrow);
// initialize service discovery manager for this connection
ServiceDiscoveryManager.getInstanceFor(connection);
return connection;
}
}

View file

@ -0,0 +1,174 @@
/**
*
* Copyright 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.jivesoftware.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.XmlUtil;
/**
* This class can be used in conjunction with a mocked XMPP connection (
* {@link ConnectionUtils#createMockedConnection(Protocol, org.jxmpp.jid.EntityFullJid, org.jxmpp.jid.DomainBareJid)}) to
* verify an XMPP protocol. This can be accomplished in the following was:
* <ul>
* <li>add responses to packets sent over the mocked XMPP connection by the
* method to test in the order the tested method awaits them</li>
* <li>call the method to test</li>
* <li>call {@link #verifyAll()} to run assertions on the request/response pairs
* </li>
* </ul>
* Example:
*
* <pre>
* <code>
* public void methodToTest() {
* Stanza stanza = new Packet(); // create an XMPP packet
* StanzaCollector collector = connection.createStanzaCollector(new StanzaIdFilter());
* connection.sendStanza(packet);
* Stanza reply = collector.nextResult();
* }
*
* public void testMethod() {
* EntityFullJid userJid = JidCreate.entityFullFrom("user@xmpp-server.org");
* DomainBareJid serverJid = JidCreate.domainBareFrom("user-server.org");
* // create protocol
* Protocol protocol = new Protocol();
* // create mocked connection
* XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, userJid, serverJid);
*
* // add reply stanza to protocol
* Stanza reply = new Packet();
* protocol.add(reply);
*
* // call method to test
* methodToTest();
*
* // verify protocol
* protocol.verifyAll();
* }
* </code>
* </pre>
*
* Additionally to adding the response to the protocol instance you can pass
* verifications that will be executed when {@link #verifyAll()} is invoked.
* (See {@link Verification} for more details.)
* <p>
* If the {@link #printProtocol} flag is set to true {@link #verifyAll()} will
* also print out the XML messages in the order they are sent to the console.
* This may be useful to inspect the whole protocol "by hand".
* </p>
*
* @author Henning Staib
*/
public class Protocol {
/**
* Set to <code>true</code> to print XML messages to the console while
* verifying the protocol.
*/
public boolean printProtocol = false;
// responses to requests are taken form this queue
private final Queue<Stanza> responses = new LinkedList<>();
// list of verifications
private final List<Verification<?, ?>[]> verificationList = new ArrayList<>();
// list of requests
private final List<Stanza> requests = new ArrayList<>();
// list of all responses
private final List<Stanza> responsesList = new ArrayList<>();
/**
* Adds a responses and all verifications for the request/response pair to
* the protocol.
*
* @param response the response for a request
* @param verifications verifications for request/response pair
*/
public void addResponse(Stanza response, Verification<?, ?>... verifications) {
responses.offer(response);
verificationList.add(verifications);
responsesList.add(response);
}
/**
* Verifies the request/response pairs by checking if their numbers match
* and executes the verification for each pair.
*/
@SuppressWarnings("unchecked")
public void verifyAll() {
// CHECKSTYLE:OFF
assertEquals(requests.size(), responsesList.size());
if (printProtocol)
System.out.println("=================== Start ===============\n");
for (int i = 0; i < requests.size(); i++) {
Stanza request = requests.get(i);
Stanza response = responsesList.get(i);
if (printProtocol) {
System.out.println("------------------- Request -------------\n");
System.out.println(XmlUtil.prettyFormatXml(request.toXML()));
System.out.println("------------------- Response ------------\n");
if (response != null) {
System.out.println(XmlUtil.prettyFormatXml(response.toXML()));
}
else {
System.out.println("No response");
}
}
Verification<Stanza, Stanza>[] verifications = (Verification<Stanza, Stanza>[]) verificationList.get(i);
if (verifications != null) {
for (Verification<Stanza, Stanza> verification : verifications) {
verification.verify(request, response);
}
}
}
if (printProtocol)
System.out.println("=================== End =================\n");
// CHECKSTYLE:ON
}
/**
* Returns the responses queue.
*
* @return the responses queue
*/
protected Queue<Stanza> getResponses() {
return responses;
}
/**
* Returns a list of all collected requests.
*
* @return list of requests
*/
public List<Stanza> getRequests() {
return requests;
}
}

View file

@ -0,0 +1,105 @@
/**
*
* Copyright 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.jivesoftware.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
/**
* Implement this interface to verify a request/response pair.
* <p>
* For convenience there are some useful predefined implementations.
*
* @param <T> class of the request
* @param <S> class of the response
*
* @author Henning Staib
*/
public interface Verification<T extends Stanza, S extends Stanza> {
/**
* Verifies that the "To" field of the request corresponds with the "From" field of
* the response.
*/
Verification<Stanza, Stanza> correspondingSenderReceiver = new Verification<Stanza, Stanza>() {
@Override
public void verify(Stanza request, Stanza response) {
assertEquals(response.getFrom(), request.getTo());
}
};
/**
* Verifies that the type of the request is a GET.
*/
Verification<IQ, Stanza> requestTypeGET = new Verification<IQ, Stanza>() {
@Override
public void verify(IQ request, Stanza response) {
assertEquals(IQ.Type.get, request.getType());
}
};
/**
* Verifies that the type of the request is a SET.
*/
Verification<IQ, Stanza> requestTypeSET = new Verification<IQ, Stanza>() {
@Override
public void verify(IQ request, Stanza response) {
assertEquals(IQ.Type.set, request.getType());
}
};
/**
* Verifies that the type of the request is a RESULT.
*/
Verification<IQ, Stanza> requestTypeRESULT = new Verification<IQ, Stanza>() {
@Override
public void verify(IQ request, Stanza response) {
assertEquals(IQ.Type.result, request.getType());
}
};
/**
* Verifies that the type of the request is an ERROR.
*/
Verification<IQ, Stanza> requestTypeERROR = new Verification<IQ, Stanza>() {
@Override
public void verify(IQ request, Stanza response) {
assertEquals(IQ.Type.error, request.getType());
}
};
/**
* Implement this method to make assertions of the request/response pairs.
*
* @param request the request collected by the mocked XMPP connection
* @param response the response added to the protocol instance
*/
void verify(T request, S response);
}