SipUnit Home
SipUnit API (Javadoc)
Developer Howto
|
SipUnit User
Guide
This guide explains what SipUnit is and how to use it to write
automated tests that verify SIP applications.
SipUnit - What Is It?
SipUnit is an open
source Java testing framework used to
write and run automated tests for verifying SIP applications. It
extends the JUnit
test
framework and adds SIP-specific
assertions along with a high-level API for performing the SIP
operations needed to interact with or invoke a test target. SipUnit
uses the JAIN-SIP
reference implementation as its underlying SIP stack/engine.
A test
program using the SipUnit API is written in Java and looks just like a
JUnit test program - a class consisting of testXxx() methods that
perform some actions and assert the expected results. Since the test
target is a SIP entity running somewhere in the network, a SipUnit
test acts as a network
element that sends/receives SIP requests and responses to interact with
the test target. The
SipUnit API includes SIP User Agent Client (UAC), User Agent Server
(UAS), and basic UAC/UAS Core functionality - the set of
processing functions that resides above the SIP transaction and
transport
layers - for the purpose of interacting with the test target.
A test program using SipUnit API:
- Extends SipTestCase
- Creates SipUnit API objects - SipStack, SipPhone, SipCall,
etc. - to handle the SIP operations that will invoke the test target
- Calls methods on the object(s) to set up and initiate
action toward a SIP test target. For example:
The method SipPhone.makeCall("sip:roger@nist.gov", SipResponse.OK,
....) makes a
vanilla call to sip:roger@nist.gov and blocks until an OK is received
or a timeout occurs. The test target could be any node up to
and including the final destination of the INVITE request message.
- Verifies the results of the action involving the test
target using both the SIP-specific assert methods provided by SipUnit
and the standard JUnit assert methods. For example:
assertHeaderContains(sipCall.getLastReceivedResponse(), "From",
"sip:amit@nist.gov"), assertEquals("Unexpected response received",
SipResponse.OK, sipCall.getReturnCode()).
SipUnit Goals
The
goals of SipUnit are:
- to provide a useful set of SIP-related assert methods
- to provide a flexible API that abstracts the
details of SIP
messaging/call handling so that a test program can interact with a SIP
test target at the level it needs to - but no more - and focus on the
tests themselves
- to facilitate free-flowing, sequential test
code so
that a test target can be exercised quickly and painlessly.
What You Need to Know Before
Using SipUnit
In order to use SipUnit effectively, you need to understand and be
proficient with:
It will help if you know the following JAIN-SIP
API classes for purposes of using the low-level methods of the
SipUnit API and examining received requests and responses in detail,
but if you need to you can learn them as you go along:
- Request, Response
- AddressFactory, HeaderFactory, MessageFactory.
Click here to see an
example of test code using the JAIN-SIP API classes.
Installing
SipUnit
Before installing SipUnit: Java (J2SE) 1.4.x or 1.5.x is required - if
you
don't
already have it, get
and install J2SE.
To install SipUnit, unzip the sipunit-<version>.zip file.
When you do this, you'll see the following contents:
sipunit
|
|------------ sipunit_license.txt
|
|------------ README
|
|------------ docs // contains
documentation
|
|
|
|------------ license
// contains the different licenses of bundled third-party software
|
|
|
|------------
api
// contains the javadoc
|
|
|
|------------ developer // diagrams for
explanation and documentation
|
|------------
lib
// contains the jars required to build, trace, and run SipUnit tests
|
|------------ examples/org/cafesip/sipunit/test
// some example SipUnit tests (used to test SipUnit
itself)
Add the jars in the sipunit/lib directory to your CLASSPATH before
executing any of the examples or your own SipUnit test class. That's
all
there is to it. Try testing out the installation, below.
Testing the Installation -
Running an Example Test
You can check SipUnit out right away by running the TestNoProxy class
in the
sipunit/examples directory (instructions below). Those tests should
pass OK. For running some of the other
example
tests,
a proxy is required. See the section on proxy
considerations for details on how to do this and also look at the
header comments in each test java file for proxy settings specific to a
particular test class.
TO RUN THE EXAMPLE TESTS: Since any test programs using SipUnit are an
extension of the JUnit framework, they need to be run like a JUnit test
program, using a TestRunner. The command line way of doing this
is shown in the example below. IDEs are pretty handy for running JUnit
tests. More detail on running JUnit
tests... Make sure the sipunit/examples directory and the jars in
sipunit/lib are in the classpath, either by setting the CLASSPATH
environmental variable beforehand to include them or by including them
in the run command using the -cp option (as below). Also, when
specifying the example test class to run, include the package prefix
(ie, org.cafesip.sipunit.test.classname).
EXAMPLE: to run the TestNoProxy test class, execute the following java
command all
on one line (for Windows, in this example) from the sipunit
directory:
C:\sipunit> java -cp
examples;lib\junit.jar;lib\sipunit.jar;lib\JainSipApi1.2.jar;lib\JainSipRi1.2.jar;lib\concurrent.jar;lib\log4j-1.2.8.jar
junit.textui.TestRunner org.cafesip.sipunit.test.TestNoProxy
You should see the time it took to run and "OK (n tests)" when it
finishes.
SipUnit Architecture - Where
Your Test Fits In
The diagram below shows
the overall architecture. Your test class uses the SipUnit API, and the
SipUnit API uses the JUnit
API and the JAIN
SIP API. The
JUnit framework provides the test
execution environment and some generic assert methods. The
JAIN-SIP engine provides the capability to interact with the test
target.
Items in blue are used to invoke and interact with the test target.
Which SipUnit classes and methods you use depends on the level of
abstraction your test can get away with. The higher, the better - the
easier and quicker it will be to write/maintain tests. More on this
next.
Items in green are used to verify the results of the interaction and
provide the test execution environment.

If you want to see a more detailed illustration, check out
the class diagram.
The Key SipUnit API Classes - How to
Interact With Your Test Target
A major SipUnit goal is to have a flexible API that
abstracts the
details of SIP
messaging/call handling so that a test program can interact with a SIP
test target at the level it needs to - but no more.
This means that if you need an established (answered) SIP call before
your first test can be executed, you should be able to establish the
call (and assert that it was successful) in one step, as part of the
setUp() method. This would be the highest level of abstraction - ie,
SipPhone.makeCall(). You tell that method what response you are
expecting (in this case, OK) and it takes care of performing the call
from sending the INVITE to receiving responses up to and including the
one you are looking for (OK).
On the other hand, if your test is focused on the call establishment
process itself, then you'll want to break the call establishment down
at least to the next level so that you can see intermediate responses,
insert your test logic at the
appropriate place and validate the result. For example, suppose you
want to verify that your test target responds to an INVITE with a
provisional response followed by OK. You would use
SipCall.initiateOutgoingCall() to send the INVITE to the test target
and then call
SipCall.waitOutgoingCallResponse() repeatedly to see each response sent
by the test target.
At a lower level still, you may need to compose the SIP messages
yourself (for example, to insert invalid headers/content) or you may
want to control the call messaging/handling in a certain, non-typical
way.
Providing these various levels of abstraction are the primary SipUnit
classes used for interacting with test targets:
- SipPhone - high
level - notable SIP operations include register(), unregister(),
makeCall(), and buddy list management methods.
- SipCall - high
level - provides outbound call handling methods, inbound call handling
methods, and various miscellaneous functions such as disconnect().
- Subscription -
high level - provides step by step control of buddy SUBSCRIBE/NOTIFY
sequences and maintains presence information.
- SipSession -
low level - provides send-request/receive-response and
receive-request/send-response methods which are used by SipCall, Subscription and SipPhone, and which are available
to user test programs to use. SipSession
methods require use of the JAIN-SIP API Request and
Response related classes including the SipStack.getXxxFactory() objects
for dealing with message, address and header content.
For an example of test code using the high level call processing
classes and methods,
click here.
For an example of test code using the buddy list subscription
capabilities, click here. (You might
want to read the Presence section before looking at the example.)
For an example of test code using the low level SipSession methods,
click here.
As always, you want to try to pick the best tool for the job at hand.
How Do You
Decide Which SipUnit Classes/Methods to Use for a
Particular Test Task?
Call Processing
Tasks
First check out the available
SipCall/SipPhone methods. If those are sufficient for the task,
then interact with the test target at that level. If your test needs to
see more details than are provided at this level, then you'll need to
use the SipSession methods.
For example: suppose your test is expecting an incoming call. You can
use the SipCall.waitForIncomingCall() method which will report the
first INVITE message that comes in. However, note from the SipCall
javadoc for this method that any non-INVITE requests received prior to
receiving an INVITE are discarded - this means your test won't see
those. If you don't care about them, then SipCall.waitForIncomingCall()
is fine to use here. But if you need to be able to see any pre-INVITE
requests that might come in while waiting for the INVITE, then use the
SipSession.waitRequest() method instead which you can call repeatedly
to see every received request message including the INVITE you were
waiting for.
Another consideration in deciding which methods to use: the SIP
messages sent by SipCall/SipPhone methods
currently default some of the header content and typically
send the minimum content required. You may have tests where you need to
add
additional message headers or specify different content than the
default. In
these cases, you'll have to create the SIP message yourself (using the
JAIN-SIP Request or Response related classes, see an example of this here)
and then use the SipSession methods
to send it. (As
a possible future enhancement, the SipCall/SipPhone
methods could be expanded to accept optional header content and/or
override default content). Why bother with SipUnit at this point?
Because still, it is handling all of the details of using the
SipStack/engine and providing conveniences such as blocking while
waiting for a response.
A quick word on blocking, then a call flow example illustrating use
of the SipUnit API calls.
Presence Tasks
This category of tasks is more straight-forward. You'll use SipPhone
methods to manipulate the phone's buddy list and also whenever you want
to initiate a single-shot fetch. After that, for a given subscription
(a persistent buddy or a fetch), you'll use its Subscription object
methods to step through SUBSCRIBE/NOTIFY sequence(s) and to view the
presence status and information which is maintained by the Subscription
class.
As far as customizing the content of messages sent by the test program
side: you may vary the default (correct) content of SUBSCRIBE requests
and NOTIFY responses sent out or provide your own incorrect/malformed
message (with the exception of the very first SUBSCRIBE message) to
send out anywhere along the sequence in order to see what the test
target does. The reason for the exception is because a correct initial
SUBSCRIBE request is needed to initialize the Subscription object
properly. If you want to send out a bad initial SUBSCRIBE message to
see what your test target does, use SipSession to send the request and
get the resulting response.
Blocking vs. Non-Blocking
Operations
Many of the methods on the SIP operation objects (SipPhone, SipCall,
etc.) block until the
invoked operation is completed. The reason for this is so that the code
in the user test class can flow logically and sequentially, in a
step-by-step fashion, without worrying about handling asynchronous
callbacks, timeouts, etc.
However, some methods are intentionally non-blocking so that the test
code can mark a point in the test execution where the API should start
doing
something, the test can continue with some other task(s), then when it
is ready, the test can block on the API action previously
initiated. This is especially needed so that a test method can
handle both endpoints of a call.
For example:
SipCall a = ua.createSipCall(); //
calling side
SipCall b = ub.createSipCall(); // callee side
b.listenForIncomingCall(); //
non-blocking - start looking for a received INVITE request and save it
a.initiateOutgoingCall("sip:becky@nist.gov", null); // this is
where the INVITE will get sent to b
assertLastOperationSuccess("a initiate call - " + a.format(), a);
b.waitForIncomingCall(10000); //
blocking - retrieve the INVITE
received by now, or wait for it to come in
assertLastOperationSuccess("b wait incoming call - " + b.format(),
b);
b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0);
assertLastOperationSuccess("b send RINGING - " + b.format(), b);
etc.
This is an example of a test acting out both sides of a call ('a' calls
'b'). The non-blocking version of waitForIncomingCall() -
listenForIncomingCall() - is needed so that the test program can tell
the API to start collecting message(s) coming in (with the 'listen')
but the test can continue executing and doing what it needs to do until
it is at a point where it is ready to be blocked waiting for the
message(s) coming in. This is also shown in the following example.
Call Flow
Example
This example shows sample SipUnit API calls (on the left) that would
appear in a test
program interacting with a test target and the resulting SIP call flow.
The blocking SipUnit methods are shown in italics. See the test class code that
was used to
generate this trace. The trace graphic was generated by the trace viewer tool.
Here, user a calls user b (both on the same box -
192.168.1.101 - but with a proxy - 127.0.0.1 - inbetween), user
b sends RINGING and OK, the test verifies these are received
by user a, then the call proceeds forward.
Execution flow in a test class method using SipUnit:
b.listenForIncomingCall()
SEND INVITE:
a.initiateOutgoingCall("sip:becky@nist.gov", null)
WAIT FOR INVITE:
b.waitForIncomingCall(5000)
SEND RESPONSE:
b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0)
WAIT FOR RESPONSE:
a.waitOutgoingCallResponse(10000)
(waitOutgoingCallResponse() returns and 'a' processes ringing)
SEND ANOTHER RESPONSE:
b.sendIncomingCallResponse(Response.OK, "Answer - Hello world", 0)
WAIT FOR RESPONSE:
a.waitOutgoingCallResponse(10000)
(waitOutgoingCallResponse() returns and 'a' processes OK)
a.sendInviteOkAck();
etc.
|
|
Authentication Support
If your test environment
involves authentication and you are using the high-level classes
(SipPhone, SipCall, Subscription, etc.), you'll likely need to make use
of the
Credential class. For each secure realm that a test call will be
sending requests to, create an instance of Credential
and pass it to SipPhone's credentials list (via the
SipPhone.addUpdateCredential() method). Do this before invoking the SIP
operation that will result in sending a request. Add as many credential
objects as there are distinct
secure realms that your test call will be accessing. You can add/remove
credentials as needed using the SipPhone credential methods.
When an
authentication challenge is received during registration, call
processing, subscribing, etc. the appropriate credential(s) from
SipPhone's list will be
automatically used to respond to the challenge. These authorization
headers
created by SipUnit in response to authentication
challenges are stored by SipPhone on a per Call-ID, per realm, basis
and are included in subsequent requests sent by SipUnit class
methods for the same Call-ID. See design
notes if you'd like more detail.
Alternatively, if all you need is to simply handle one authentication
challenge for registration, you can just use the SipPhone.register()
method that takes user name and password parameters. In this case you
don't need to bother with using the Credential class.
SipUnit supports Digest
Access Authentication.
Presence Support
This section describes the
presence capabilities implemented in SipUnit thus far and how you can
use them in your test program.
What Type of
Presence Does SipUnit Support? What Types Are There?
A test program using the
primary SipUnit presence capabilities (SipPhone buddy methods and
Subscription class methods) can act as a Type II (Distributed Agents*)
presence client for the purpose of testing a presence server or
server-side application. That is, it can send SUBSCRIBE messages to a
presence server and receive/validate NOTIFY messages from the presence
server. Your test program
can control the contents of SUBSCRIBE requests and NOTIFY responses
sent to the server. By default, the messages are correctly formed and
the content is valid. Type II client "Subscriber" emulation for testing
presence server and server-side applications is the primary focus of
the SipUnit presence feature, at least so far.
A test program using
the primary SipUnit presence capabilities (SipPhone buddy methods,
Subscription class methods) does
not communicate directly with other clients like a Type I (Co-located
Agents*) client does - that is, it does not also receive SUBSCRIBE and
send NOTIFY messages. It cannot be used to test a Type I client.
A test program using the SipUnit PresenceNotifySender utility class can
simulate the presence server side for the purpose of testing Type II
clients. That is, it can receive SUBSCRIBE messages and send NOTIFY
messages. Your test program tells it the values to send in SUBSCRIBE
responses (status code, reason phrase) and NOTIFY requests (presence
info, subscription state, etc.).
A test class method using both the primary SipUnit presence capabilities (SipPhone buddy methods and Subscription
class methods) for one side
of a subscription (the "Subscriber") and the PresenceNotifySender utility class for
the other side of the subscription (the "Notifier") can be used to
test/generate SUBSCRIBE/NOTIFY traffic through network elements such as
a proxy.
*Check out this white paper for a nice
explanation of presence and the different types of server-side
capabilities and clients.
What Features
Does SipUnit Presence Support? What RFCs Does it Implement?
SipUnit presence supports:
- a buddy list per SipPhone that maintains presence
information for active and expired subscriptions
- buddy list operations - add/refresh/remove buddies from the
buddy list
- "application/pidf+xml" presence data format
- multiple devices for a presentity (ie, multiple tuples in a
NOTIFY message) along with other RFC3863 NOTIFY xml body elements
(except for extensions)
- validation per RFC of received SUBSCRIBE responses, NOTIFY
requests
- authentication challenge handling for sent SUBSCRIBEs
(DIGEST method only)
- user control over the event "id" and other information sent
in a given SUBSCRIBE request or NOTIFY response.
Pertinent RFCs that SipUnit presence follows with respect to
"Subscriber"-side presence and event package handling (limitations
noted next section):
- RFC-3261 - SIP: Session Initiation Protocol
- RFC-3856 - A Presence Event Package for the Session
Initiation Protocol (SIP)
- RFC-3265 - Session Initiation Protocol (SIP)-Specific Event
Notification
- RFC-3863 - Presence Information Data Format (PIDF).
What are the
Limitations? What Doesn't It Do?
Currently the following is NOT supported:
- multiple subscriptions in the same dialog
- automatic refresh/keep alive of subscriptions
- sending authentication challenge for received NOTIFY
messages
- extensions and mustUnderstands in the NOTIFY body
(currently ignored)
- PUBLISH method at a high level (can be done using low-level
SipSession methods to send a request)
Overview of
Operation
Here's how it works:
- Just like a normal call processing test, your presence test
class creates one or more SipPhone objects. SipPhone now supports buddy
list operations and single-shot fetches in addition to its previous
capabilities. If authentication will be required, give the necessary
credentials to the SipPhone by calling addUpdateCredential().
- Next, call a buddy or fetch method on the SipPhone. Let's
say you're starting a buddy list so you call SipPhone.addBuddy() for
the first buddy. This causes a SUBSCRIBE message to be created and sent
to the server/target, and your test program is blocked on the
addBuddy() method call until a first response is received. Assuming a
favorable response (as opposed to a fatal error response), addBuddy()
returns a Subscription object that will represent this
buddy/subscription for the life of the subscription.
- At this point, you'll now use methods of the returned
Subscription class to step through the
remainder of this SUBSCRIBE/NOTIFY sequence. (Note, you may also be
doing other things with the SipPhone or with other buddies as well,
besides this particular sequence). You may check the status code of the
initial response that was received before calling
processSubscribeResponse() which will complete the SUBSCRIBE
transaction handling. This is another blocking method that processes
the response just received and handles the remainder of the SUBSCRIBE
operation, validating message(s) received from the Far End. When the
SUBSCRIBE transaction is over, processSubscribeRespons() returns a
success/fail indication to your test program. Assuming success, you can
proceed by checking resulting info as shown in the diagram.
- Your next step, whenever your test program is ready, is to
collect the NOTIFY message from the Far End by calling waitNotify().
This is a blocking method that waits for a NOTIFY message to come in if
one hasn't come in already as a result of the SUBSCRIBE sequence or for
any other reason. Once a NOTIFY is received, waitNotify() returns the
received request to your
program as a RequestEvent object. You may examine the NOTIFY request
yourself if you want to use the JAIN SIP API to do it, but you
don't have to. Pass the NOTIFY object to processNotify()
which will do several things:
- validate the content of the received NOTIFY, body
included
- update the Subscription object with the received
information (presence tuples, subscription state, etc)
- create the proper, correct NOTIFY response that
should be
sent back to the Far End (but don't sent it yet)
The composed response is returned to you by processNotify(). You may
examine it yourself, or even modify/corrupt it,if you want to use the
JAIN SIP API to do it,
but you don't have to. Regardless, you can see the NOTIFY processing
results by calling the getXyz() methods on the Subscription object as
shown in the diagram below.
- Finally, call replyToNotify(), passing it the response to
send to the Far End. You get back an indication of whether or not the
message was sent
successfully.
That completes the handling of a SUBSCRIBE/NOTIFY sequence. It might
seem like alot
just to get through one sequence, but you'll see that your test program
is straight-line and straigtforward. Also, these steps can be put in a
single blocking convenience method if this sequence is not the focus of
the test method. A refreshBuddy() and removeBuddy()
works just the same way, except they don't create a new Subscription
object (the existing one continues to be used).
You can see an example of test code using the buddy list subscription
capabilities clicking here.
For even more detail on what is happening behind the scenes, continue
on to the next section.

More Detail on
What SipUnit is Doing
The following diagram elaborates on the previous section, giving some
more details on what the Subscription class is doing and checking for.
Take a look also at the Javadoc for the SipPhone and Subscription
methods shown here to see additional details like parameters, etc.

Where's An
Example I Can Look At?
For an example of test code using the buddy list subscription
capabilities, click here.
Also look in the examples directory of the download package for many
more examples.
STUN Support
If you want to run your SipUnit test from behind a NAT and register
with a proxy on the public internet, you need to do a couple of extra
things in your test program.
First, at the beginning your test and before creating your SIP Stack
and any SipPhones/SipSessions, determine the public IP address/port for
your SipUnit test program to use. For this, you can use the stun4j API
(the jar
is in the SipUnit lib) to access a STUN server on the internet and send
messages to it, and from that, you'll be able to find out your public
address/port. Use the example code below
- the getPublicAddress() method will do all of this job for you. If the
STUN server used in the example code (stun.fwdnet.net) doesn't work,
there are some public ones freely available for
this purpose - google for it or visit
http://www.voip-info.org/wiki-STUN which lists some STUN servers and
also has other useful information.
Second, once you've determined your public address as described above
and then created your SipUnit SipStack and SipPhone/SipSession
(as usual, using your private address), you can then call the method
setPublicAddress(publichostaddress, publicport) on
your SipPhone/SipSession. This method replaces the private host/port
values in
this Sip agent's contact address, via, and listening point 'sentby'
components with the public host and port parameters you pass in (ie,
the publicly accessible address). Then whenever your
SipPhone/SipSession communicates with
the public SIP server on the internet, the SIP server will be able to
communicate back to your test program via its public address. See the
TestWithStun.java file in the sipunit test/examples directory for a
full example of how to do it (snippets below). You can try out that
test file by following the instructions in the file header comments.
At any point in your test program, if you need to find out the public
address you're using, call the SipPhone or SipSession method
getStackAddress().
Example Code
Here are snippets from the TestWithStun.java test file in the sipunit
test/examples directory. The setUp() method calls getPublicAddress()
which uses a public STUN server to determine the publicly accessible IP
address and port to use. Then the setUp() method creates the SipStack
and SipPhone, calling setPublicAddress() on the SipPhone so that it
will be able to receive messages from the SIp server on the internet.
See TestWithStun.java test file in the sipunit test/examples directory
for complete details.
public void setUp() throws Exception
{
// use the stun server to
find out my public address
assertTrue(getPublicAddress());
try
{
sipStack = new SipStack(testProtocol, myPort, properties);
SipStack.setTraceEnabled(true);
}
catch (Exception ex)
{
fail("Exception: " + ex.getClass().getName() + ": "
+ ex.getMessage());
throw ex;
}
SipStack.trace("My public IP
address = " + publicIP + ", port = "
+ publicPort);
try
{
ua =
sipStack.createSipPhone(properties
.getProperty("sipunit.proxy.host"), testProtocol,
proxyPort, myUrl);
ua.setPublicAddress(publicIP, publicPort);
}
catch (Exception ex)
{
fail("Exception creating SipPhone: " + ex.getClass().getName()
+ ": " + ex.getMessage());
throw ex;
}
}
public boolean getPublicAddress()
{
StunAddress localAddr = null;
StunAddress serverAddr =
null;
try
{
localAddr = new StunAddress(InetAddress.getLocalHost()
.getHostAddress(), myPort);
}
catch (UnknownHostException
e)
{
e.printStackTrace();
return false;
}
serverAddr = new
StunAddress("stun.fwdnet.net", 3478);
NetworkConfigurationDiscoveryProcess addressDiscovery = new
NetworkConfigurationDiscoveryProcess(
localAddr, serverAddr);
try
{
addressDiscovery.start();
}
catch (StunException e)
{
e.printStackTrace();
return false;
}
StunDiscoveryReport
stunReport = null;
try
{
stunReport = addressDiscovery.determineAddress();
}
catch (StunException e)
{
e.printStackTrace();
return false;
}
StunAddress stunAddress =
stunReport.getPublicAddress();
publicIP =
stunAddress.getSocketAddress().getAddress().getHostAddress();
publicPort =
stunAddress.getSocketAddress().getPort();
// must shutdown stun
process, otherwise, stun4j is holding on the same
// udp port
addressDiscovery.shutDown();
return true;
}
The SipTestCase Class
The SipTestCase class is nothing more than a simple class that extends
the JUnit TestCase class and has some of its own SIP-specific
assertXxx() methods. It serves as the base class for a SIP application
test program, or test class. While other SipUnit API objects like
SipCall and SipSession interact with a test target, SipTestCase methods
only play a role in the validation of results. The SipTestCase assert
methods along with the JUnit Assert class methods are used by a test
class to check the results of each test action it performs.
The SipTestCase assert methods focus on SIP-specific items of interest
such as checking if a certain header is present in a SIP message, or if
the SIP message body contains a specific string, etc. Also there are
methods to check if the last SIP operation was successful or not, given
the SipUnit API object that performed the operation (ie, SipCall,
SipPhone, or SipSession). These API objects support an interface that
allows all error/status details to be output if an assertion fails.
Failure Output
Like JUnit, messages are output only if a test fails. The SIP
application test class controls what message text output, if any,
should be generated if an assertion fails. Additionally, when a test
fails, you'll see a stack trace dump just like with JUnit.
Unlike JUnit, your test class line of code won't be the first entry in
the stack trace output, in the current release - there's a TODO to
adjust the stack so that entries at the SipTestCase level are
discarded. In the meantime, you'll have to skip over the first couple
or few lines when looking at the stack trace to find the line of code
in your test class where the test failed.
Putting It All Together -
How To Write Your Test Class
Writing a test class that uses the SipUnit API is just like
writing a JUnit test class, except for the first step:
- implement a subclass of SipTestCase
- define instance attributes that store information needed
by each test in the class
- initialize the setup for each test method by overriding
method
setUp()
- clean-up after each test by overriding tearDown()
- write one or more tests, each of which is a method
beginning with the letters "test" (ie, testMakeCall()).
The code in each of the test methods will:
- create SipUnit
API objects (ie, SipPhone, SipCall, etc.)
- call method(s) on the objects to set up and take some kind
of action against the test target
- check the results using assert methods found in the SipTestCase
class and the JUnit Assert
class.
Check out the following example. You can also look at the source code
in the sipunit examples directory which consists of test classes that
test
out the API itself.
Some Example Test Class Excerpts
Here are some example test class snippets that use the SipUnit API.
You may want to look at the SipUnit javadoc while looking at this so you
can see what some methods
like format() do. This test uses a proxy - not important for
purposes of this example, but you can see Proxy Considerations for details on
testing with and without a proxy.
Just Like JUnit
Like with JUnit, a test class using SipUnit extends
JUnit TestCase - by way of SipTestCase - and contains setUp(),
tearDown(), and one or more testXxx()
methods.
import
org.cafesip.sipunit.SipCall;
import
org.cafesip.sipunit.SipPhone;
import
org.cafesip.sipunit.SipResponse;
import
org.cafesip.sipunit.SipStack;
import
org.cafesip.sipunit.SipTestCase;
import
org.cafesip.sipunit.SipTransaction;
public class
TestWithProxyNoAuthentication extends
SipTestCase
{
private
SipStack sipStack;
private
SipPhone ua;
....
public void setUp() throws
Exception
{
sipStack = new SipStack(null, -1);
ua = sipStack.createSipPhone(..., "sip:amit@nist.gov"); // create user a
}
public void tearDown() throws
Exception
{
ua.dispose();
sipStack.dispose();
}
/**
* This
test illustrates usage of SipTestCase. In it, user a calls user b, user
b sends RINGING and OK,
* the test verifies these are received
by user a, then the call proceeds through disconnect (BYE).
*/
public void testBothSidesCallerDisc()
{
ua.register(null, 1800);
assertLastOperationSuccess("Caller registration failed - "
+ ua.format(), ua);
try
{
SipPhone ub = sipStack.createSipPhone(..., "sip:becky@nist.gov"); // create
user b
ub.register(null, 600);
assertLastOperationSuccess(ub); // standard JUnit fail output
SipCall a = ua.createSipCall();
SipCall b = ub.createSipCall();
b.listenForIncomingCall();
Thread.sleep(10);
a.initiateOutgoingCall("sip:becky@nist.gov",
null);
.... <etc., like Call Flow Example
above>
A Little Can Do
Alot
A short test method can exercise alot of functionality.
public
void testBothSidesAsynchMakeCall() //
a calls b via proxy, testing the
proxy
{
assertTrue(ua.register("amit", "a1b2c3d4", null, 0, 10000));
try
{
SipPhone ub = sipStack.createSipPhone(..., "sip:becky@nist.gov");
assertTrue(ub.register("becky", "a1b2c3d4", null, 600, 5000));
SipCall b = ub.createSipCall();
// prepare the called side
b.listenForIncomingCall();
Thread.sleep(50);
// make the call - a calls b
SipCall a = ua.makeCall("sip:becky@nist.gov", null);
assertLastOperationSuccess(ua.format(), ua);
assertTrue(b.waitForIncomingCall(0)); // blocks until b receives INVITE
// b sends RINGING
assertTrue(b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0));
Thread.sleep(500);
// b sends answer
assertTrue(b.sendIncomingCallResponse(Response.OK, "Answer - Hello
world", 0));
Thread.sleep(500);
// verify RINGING was received
by a
assertResponseReceived("Should have gotten RINGING response",
SipResponse.RINGING, a)
;
// verify OK was received
by a
assertAnswered("Outgoing call leg not answered", a);
//
finish off the call
assertTrue(a.sendInviteOkAck());
assertTrue(a.listenForDisconnect());
Thread.sleep(100);
assertTrue(b.disconnect());
assertTrue(a.waitForDisconnect(5));
assertTrue(a.respondToDisconnect());
ub.dispose();
}
catch (Exception e)
{
e.printStackTrace();
fail("Exception: " + e.getClass().getName() + ": " + e.getMessage());
}
}
There are Many
Ways to Skin a Cat
Your test code can verify expected results in different ways - they all
have their plusses and minuses.
--------------------------------------------------
a.initiateOutgoingCall("sip:becky@nist.gov",
null);
assertLastOperationSuccess("a
initiate call - " + a.format(), a);
Outputs operation error details if the test fails, but requires two
lines of code.
--------------------------------------------------
boolean status_ok =
a.initiateOutgoingCall("sip:becky@nist.gov", null);
assertTrue("Initiate outgoing
call failed - " + a.format(), status_ok);
Same as above - except now the SIP action (initiateOutgoingCall) is
hidden in the middle of a line and harder to see.
--------------------------------------------------
assertTrue("Initiate outgoing
call failed", a.initiateOutgoingCall("sip:becky@nist.gov", null));
Nice and compact, but...
- no operation error details if the test fails - a.format() as a first
parameter won't work (won't have the results yet to format)
- the SIP action is hidden in the middle and harder to see.
An Example of Using
Low Level SipUnit Methods & the JAIN-SIP API Request/Response
Related Classes
When using the lower-level SipSession methods to send/receive SIP
messages, you'll be using the JAIN
SIP API message, address, header, request, and response classes to
build/view the SIP messages. Here is an example of test class code
operating at this level.
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
SipPhone ua = sipStack.createSipPhone("sip:amit@nist.gov");
// build the INVITE Request message
AddressFactory addr_factory = ua.getParent().getAddressFactory();
HeaderFactory hdr_factory = ua.getParent().getHeaderFactory();
Request invite = ua.getParent().getMessageFactory().createRequest(
"INVITE sip:becky@nist.gov SIP/2.0 ");
invite.addHeader(ua.getParent().getSipProvider().getNewCallId());
invite.addHeader(hdr_factory.createCSeqHeader(1, Request.INVITE));
invite.addHeader(hdr_factory.createFromHeader(ua.getAddress(), ua
.generateNewTag()));
Address to_address = addr_factory.createAddress(addr_factory
.createURI("sip:becky@nist.gov"));
invite.addHeader(hdr_factory.createToHeader(to_address, null));
Address contact_address = addr_factory.createAddress("sip:amit@"
+ thisHostAddr + ":5060");
invite.addHeader(hdr_factory.createContactHeader(contact_address));
invite.addHeader(hdr_factory.createMaxForwardsHeader(5));
ArrayList via_headers = ua.getViaHeaders();
invite.addHeader((ViaHeader) via_headers.get(0));
Address route_address = addr_factory.createAddress("sip:becky@"
+ thisHostAddr + ":5060");
invite.addHeader(hdr_factory.createRouteHeader(route_address));
// send the Request message
SipTransaction trans = ua.sendRequestWithTransaction(invite, false,
null);
assertNotNull(ua.format(), trans);
// call sent
Etc.
A Presence Example Using SUBSCRIBE/NOTIFY
This example shows a complete test method that establishes a
subscription and then verifies subscription termination from the client
side. The most important parts are in bold.
NOTE: this test was written to validate the SipUnit API itself
(subscriber client-side handling) - it uses a utility class,
PresenceNotifySender, to simulate the presence server. Your test method
wouldn't need that (simulator) side of it since your server/application
under test will respond to the SUBSCRIBE messages sent out by this test
and send NOTIFY messages to this test object. But, your test will look
like the rest of what you see below:
- you'll use a SipPhone object ("ua" below) that will have a
buddy list or be able to do a fetch (each buddy/fetch having a
Subscription object respresenting it);
- you'll call methods on the SipPhone (addBuddy(),
refreshBuddy(), fetch(), etc.) which will initiate a SUBSCRIBE/NOTIFY
sequence to your server/application;
- you'll use methods of the Subscription class to step
through the SUBSCRIBE/NOTIFY sequence and validate the messages and
presence information sent by your server/application, like you see
being done below.
private SipStack sipStack;
private SipPhone ua;
etc.
public void setUp() throws
Exception
{
try
{
sipStack = new SipStack(PROTO, MY_PORT, properties);
SipStack.setTraceEnabled(true);
}
catch (Exception ex)
{
fail("Exception: " + ex.getClass().getName() + ": "
+ ex.getMessage());
throw ex;
}
try
{
// create my test SIP phone ("ua")
ua = sipStack.createSipPhone(PROXY_HOST, PROTO, PROXY_PORT,
"sip:amit@nist.gov");
//
register with the server
ua.addUpdateCredential(new Credential(REALM, "amit", "a1b2c3d4"));
ua.register(null, 3600);
assertLastOperationSuccess(
"Caller registration using pre-set credentials failed - "
+ ua.format(), ua);
}
catch (Exception ex)
{
fail("Exception creating SipPhone: " + ex.getClass().getName()
+ ": " + ex.getMessage());
throw ex;
}
}
/*
* @see SipTestCase#tearDown()
*/
public void tearDown() throws Exception
{
ua.dispose();
sipStack.dispose();
}
public void
testEndSubscription()
{
// This method tests
terminating a subscription from the client side
// (by removing a buddy from
the SipPhone buddy list).
String buddy = "sip:becky@nist.gov"; // URI
of buddy
// test steps SEQUENCE:
// 1) prepare the far end (I
will use the PresenceNotifySender
//
utility class to simulate the Presence Server)
// 2) establish an active
subscription (SUBSCRIBE, NOTIFY)
// 3) remove buddy from
buddy list - sends SUBSCRIBE, gets response
// 4) check the return code,
process the received response
// 5) tell far end to send a
NOTIFY
// 6) get the NOTIFY
// 7) process the NOTIFY
// check
the processing results
// check
PRESENCE info - devices/tuples
// check
PRESENCE info - top-level extensions
// check
PRESENCE info - top-level notes
// 8) reply to the NOTIFY
try
{
//
(1) prepare the far end - a presence server and a buddy somewhere
//
create the far end, register buddy w/server
PresenceNotifySender pserver = new PresenceNotifySender(sipStack
.createSipPhone(PROXY_HOST, PROTO, PROXY_PORT, buddy));
boolean registered = pserver.register(new Credential(REALM,
"becky", "a1b2c3d4"));
assertTrue(pserver.getErrorMessage(), registered);
//
prepare far end to receive SUBSCRIBE within 5 sec, respond w/OK
assertTrue(pserver.processSubscribe(5000, SipResponse.OK, "OK"));
//
(2) establish a subscription, get the buddy in the buddy list
Subscription s = ua.addBuddy(buddy, 2000);
// sends SUBSCRIBE, gets response
assertNotNull(s);
assertTrue(s.processSubscribeResponse(1000));
assertEquals(SipResponse.OK,
s.getReturnCode());
assertTrue(s.isSubscriptionActive());
//
do the initial NOTIFY sequence - still establishing
String notify_body = "<?xml version=\"1.0\"
encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@nist.gov\"
xmlns=\"urn:ietf:params:xml:ns:pidf\"><tuple
id=\"1\"><status><basic>closed</basic></status></tuple></presence>";
assertTrue(pserver.sendNotify(SubscriptionStateHeader.ACTIVE, null,
notify_body, 2400, true));
RequestEvent reqevent = s.waitNotify(500);
// client receives notify
assertNotNull(reqevent);
assertNoPresenceErrors(s);
Response response =
s.processNotify(reqevent); // client processes it
assertNotNull(response); // this is
the response that should be sent back
assertTrue(s.isSubscriptionActive());
//
verify updated client PRESENCE info - still part of establishment
HashMap devices = s.getPresenceDevices();
assertEquals(1, devices.size());
PresenceDeviceInfo dev = (PresenceDeviceInfo) devices.get("1");
assertNotNull(dev);
assertEquals("closed", dev.getBasicStatus());
assertEquals(0, s.getPresenceExtensions().size());
assertEquals(0, s.getPresenceNotes().size());
//
reply to the NOTIFY
assertTrue(s.replyToNotify(reqevent,
response));
//
verify the buddy lists look correct
assertEquals(1, ua.getBuddyList().size());
assertEquals(0, ua.getRetiredBuddies().size());
assertNoPresenceErrors(s);
//
(3) Now, end the subscription from our (client) side
//
prepare far end to receive SUBSCRIBE within 5 sec, reply with OK
assertTrue(pserver.processSubscribe(5000, SipResponse.OK,
"OK Ended"));
//
remove buddy from contacts list, terminating SUBSCRIBE sequence
assertNotNull(ua.removeBuddy(buddy, 300));
//
check immediate impacts - buddy lists, subscription state
assertEquals(0, ua.getBuddyList().size());
assertEquals(1, ua.getRetiredBuddies().size());
assertNoPresenceErrors(s);
assertNotNull(ua.getBuddyInfo(buddy)); // check buddy can still be found
assertEquals(s.getBuddyUri(), ua.getBuddyInfo(buddy).getBuddyUri());
assertFalse(s.isSubscriptionActive());
assertFalse(s.isSubscriptionPending());
assertTrue(s.isSubscriptionTerminated());
String reason = s.getTerminationReason();
assertNotNull(reason);
//
(4) check the SUBSCRIBE response code, process the response
assertEquals(SipResponse.OK,
s.getReturnCode());
String
reason = s.getTerminationReason();
assertEquals("OK Ended", reason);
//
process the received response
assertTrue(s.processSubscribeResponse(300));
//
check the response processing results
assertFalse(s.isSubscriptionActive());
assertFalse(s.isSubscriptionPending());
assertTrue(s.isSubscriptionTerminated());
assertEquals(reason, s.getTerminationReason());
assertEquals(0, s.getTimeLeft());
assertEquals(0, ua.getBuddyList().size());
assertEquals(1, ua.getRetiredBuddies().size());
assertNoPresenceErrors(s);
//
(5) tell far end to send a last NOTIFY - use different tuple info
notify_body = "<?xml version=\"1.0\"
encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@nist.gov\"
xmlns=\"urn:ietf:params:xml:ns:pidf\"> <tuple id=\"3\">
<status> <basic>open</basic> </status>
</tuple> </presence>";
assertTrue(pserver.sendNotify(SubscriptionStateHeader.TERMINATED,
"done", notify_body, 0, true));
//
(6) get the NOTIFY
reqevent = s.waitNotify(300);
assertNotNull(reqevent);
assertNoPresenceErrors(s);
//
(7) process the NOTIFY
response = s.processNotify(reqevent);
assertNotNull(response);
assertNoPresenceErrors(s);
//
check the processing results
assertTrue(s.isSubscriptionTerminated());
assertNotNull(s.getTerminationReason());
assertFalse(reason.equals(s.getTerminationReason())); // updated
assertEquals(0, s.getTimeLeft());
assertEquals(SipResponse.OK,
s.getReturnCode()); // response code
//
check PRESENCE info got updated w/last NOTIFY - devices/tuples
//
-----------------------------------------------
devices = s.getPresenceDevices();
assertEquals(1, devices.size());
dev = (PresenceDeviceInfo) devices.get("3");
assertNotNull(dev);
assertEquals("open", dev.getBasicStatus());
assertEquals(-1.0, dev.getContactPriority(), 0.001);
assertNull(dev.getContactURI());
assertEquals(0, dev.getDeviceExtensions().size());
assertEquals(0, dev.getDeviceNotes().size());
assertEquals("3", dev.getId());
assertEquals(0, dev.getStatusExtensions().size());
assertNull(dev.getTimestamp());
//
check PRESENCE info - top-level extensions
//
-----------------------------------------------
assertEquals(0,
s.getPresenceExtensions().size());
//
check PRESENCE info - top-level notes
//
-----------------------------------------------
assertEquals(0,
s.getPresenceNotes().size());
//
(8) reply to the NOTIFY
assertTrue(s.replyToNotify(reqevent,
response));
}
catch (Exception e)
{
e.printStackTrace();
fail("Exception: " + e.getClass().getName() + ": " + e.getMessage());
}
}
Running Your Test
Since your test class extends SipTestCase and SipTestCase extends JUnit
TestCase, execute your test class the same way you would execute any
JUnit test class. Your CLASSPATH must have in it:
- sipunit.jar along with the other
jar files in the sipunit lib directory (nist-sip-1.2.jar, junit.jar,
etc.)
- your SipUnit test classes, and any other class files you
might be using along with the libraries they need.
It is very common to run JUnit/SipUnit test
classes using an IDE or Ant, which comes with a junit task
for running tests. For a command line example, see the instructions in
this document on testing the installation.
If you'd like to see lots of details and options on
running JUnit tests, the JUnit site has a comprehensive faq on Running
Tests. If you refer to any of these materials, keep in mind that
wherever junit.jar is referred to, the same applies to the sipunit lib
jar files.
If your test class is using a proxy, have that available and running
with the appropriate settings. See Proxy
Considerations for more detail on this subject.
Debugging Tests
To debug what's happening during a SipUnit test, turn on the SipUnit
trace by adding this line of code to your test class setUp() method:
SipStack.setTraceEnabled(true);
You'll get some console output to help you see what is happening.
Another good way to see what's going on during a test run is to use the
trace
viewer tool from the NIST-SIP software library. It shows a
graphical trace of SIP messages sent and received by your test class.
An example trace is shown at the end of this section.
The trace tool uses a trace file generated by the underlying JAIN-SIP
stack. You can control what level of tracing is to be done by the
properties parameter you pass in to the SipUnit SipStack constructor.
For the default, you don't have to pass in anything (pass in null).
This will result in minimal but usually sufficient tracing (level =
16). At this level, when you execute your test you'll see that a
testAgent_log.txt file is generated. This is the file that you'll give
as an argument to the trace viewer. For a more detailed debug-level
trace, you'll have to pass in the properties parameter to SipStack with
the trace level set to 32. At this level, when you execute your test
you'll see that a testAgent_debug.txt file is generated. This file has
alot more detail in it. This is the
file that you'll give as an argument to the trace viewer. See further
below for details on the properties you'll need to pass to SipStack for
getting the debug-level tracing turned on or look at some of the test
classes in the examples directory.
Viewing
the traces using the trace viewer tool
The jar for the trace viewer is
in the sipunit lib directory. It needs to be in your CLASSPATH or
you can specify it using the '-cp' argument.
To view level=16 (default) traces, execute the command (windows in this
example):
java -cp
C:\path-to-sipunit\lib\tracesviewer.jar tools.tracesviewer.TracesViewer
-server_file C:\path-to-execution-dir\testAgent_log.txt
To view debug-level traces, execute the command:
java -cp
C:\path-to-sipunit\lib\tracesviewer.jar tools.tracesviewer.TracesViewer
-debug_file C:\path-to-execution-dir\testAgent_debug.txt
Viewing
the trace files directly
You can skip the graphical viewing and look
at the generated trace files using an editor. You'll see that the
level-16 gives all incoming and outgoing SIP messaging traces while the
debug-level tracing adds to that alot of internal information such as
the transaction/dialog states/handling by the stack.
Details on SipStack
properties needed for generating debug-level trace files
Currently the SipUnit SipStack properties parameter is all-or-nothing,
so if you want to change any stack property - like the trace level -
you'll have to pass in the whole set of properties. Note that the
log/debug file names referred to above are also specified by the
properties.
Here is an example of what you would have in your test class:
....
private Properties properties = new Properties();
....
String host = null;
try
{
host = InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
host = "localhost";
}
properties.setProperty("javax.sip.IP_ADDRESS", host);
properties.setProperty("javax.sip.RETRANSMISSION_FILTER", "true");
properties.setProperty("javax.sip.STACK_NAME", "testAgent");
// You need 16 for logging traces. 32 for debug + traces.
// Your code will limp at 32 but it is best for debugging.
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG",
"testAgent_debug.txt");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG",
"testAgent_log.txt");
// Guard against starvation.
properties.setProperty("gov.nist.javax.sip.READ_TIMEOUT", "1000");
// properties.setProperty("gov.nist.javax.sip.MAX_MESSAGE_SIZE",
// "4096");
properties.setProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS",
"false");
....
.sipStack = new SipStack(null, -1,
properties);
An example trace
Here's an example of the default (level 16) trace. Note in the call
flow that the "OK-Answer - Hello world" message has been selected (it's
in red) and its contents are detailed in the lower left part of the
screen. You can see the contents of any message by selecting it in the
call flow diagram.

Proxy Considerations
& Routing Policies
As far as routing policies go, a test class using SipUnit can:
- define an outbound proxy on a per-SipStack basis (done via
a Properties parameter to SipUnit SipStack constructor which is the
same list of properties the JAIN-SIP SipStack class takes) to be used
at the SIP stack level for outgoing initial requests. This cannot
be changed during the life of the SipStack.
- define an outbound proxy on a per-SipPhone(/SipSession)
basis (done via host/port/protocol parms to the SipPhone constructor).
At the SipSession (low-) level, this is applied by the caller on a
per-request basis. If applied, the request URI of the given request is
modified to reflect the proxy destination. If not applied, no
modification of the request is done and no additional headers are added
before sending the request out (caller must handle details such as
adding a RouteHeader). At the SipCall/SipPhone (high-) level, this is
applied for all registrations and outbound calls, but can be overridden
on a per-outbound-call basis by specifying an alternate route to use
instead of the SipPhone's proxy.
- do neither of the above. This is only an option when using
low-level API calls and handling all message/routing details yourself,
or using high-level API calls (except SipPhone.un/registration()) and
always specifying an outbound call alternate route.
For an example of a test class not using a proxy, see the
TestNoProxy.java file in the sipunit examples directory.
For an example of a test class using a proxy, see one of the other test
files in the sipunit examples directory. It's class header comment will
indicate the required proxy settings for running that test.
Where Can I Get
a Proxy to Use?
There are a number of free, open source servers available. Check out
the jain-sip-presence-proxy
project page. Also cafesip's SipExchange
provides proxy server capabilities. There may be other free available
SIP proxies
elsewhere on the internet as well.
Acknowledgements
We would like to acknowledge
and thank the National Institute of Standards and Technology (NIST) for
providing the following useful software. This software is provided by NIST as a
service. NIST owns the software. You can find their projects page here.
The JUnit test framework was written by Erich Gamma and Kent Beck.
Visit the JUnit website
for useful links and references to other XUnit testing frameworks.
External contributors to SipUnit include:
- Venkita S. - for changes to SipSession and SipStack for
creating multiple SIP stacks independent of IP address.
- manchi - for notes on STUN support and some example code.
|