Look what Java and SIP can do
 (SIP)Java =
Home     News     Projects     Downloads     Support     Contact

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:
  1. Extends SipTestCase
  2. Creates SipUnit API objects - SipStack, SipPhone, SipCall, etc. - to handle the SIP operations that will invoke the test target
  3. 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.
  4. 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.


not available

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 may want to break the call establishment down at least to the next level so that you can see intermediate responses as they come in, 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 could 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 as it comes in. If your test program doesn't need to alter the call/test sequence based on what happens during setup, you can let SipPhone.makeCall() handle the entire call establishment and then call getAllReceivedResponses() to see the messages that came in during setup.

At a lower level still, you may need to handle the SIP messages yourself (for example, to control the call messaging/handling in a certain, non-standard way). The SipSession class allows you to do this. However, note that if you just need to insert extra or invalid message content in message headers and/or body, or replace valid message content with invalid content of your own, most of the high level SipUnit methods provide options for you to specify additional headers, replacement headers, and message body contents so that you can control the message contents to be sent out.

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(), refer(), addBuddy(), fetchPresenceInfo(), credentials management.
  • SipCall - high level - provides outbound call handling methods, inbound call handling methods, and various miscellaneous functions such as cancel and reinvite.
  • PresenceSubscriber - high level - provides step by step control of presence/buddy SUBSCRIBE/NOTIFY sequences and maintains presence information.
  • ReferSubscriber - high level - provides step by step control of REFER subscription sequences including refresh and unsubscribe.
  • SipSession  - low level - provides send-request/receive-response and receive-request/send-response methods which are used by the above classes 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 presence (buddy list) subscription capabilities, click here.
For an example of test code using the REFER subscription capabilities, click here.
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 - those will keep your test program simple. You'll notice that many of the SipCall/SipPhone methods have multiple method signatures accepting additional headers, replacement headers and message body - the purpose of that is so that you can specify optional header/body content and/or override default message content for test purposes when you need to while still using the highest level API.

If your test needs to be more reactive than is allowed at the above 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 block until an INVITE message comes in. Note from the SipCall javadoc for this method that any non-INVITE requests received prior to the INVITE are collected, and you can look at them if you need to once the INVITE has been received (since this is a blocking method). If you don't care about them, then SipCall.waitForIncomingCall() is fine to use here. But if you need to react to a received pre-INVITE request that arrives while waiting for the INVITE, at the time it comes in, then you'll need to use the SipSession.waitRequest() method instead which you can call repeatedly to see and process each received request message including the INVITE you were waiting for.

Another reason to use SipSession is if you need to have nonstandard signaling sequences in your test. 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 follows in the next subsection, then you can see 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 initiate a subscription (addBuddy()) 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 the returned PresenceSubscriber object methods to step through the SUBSCRIBE/NOTIFY sequence(s) and to view the presence status and information which is maintained by the PresenceSubscriber object.

As far as customizing the content of presence-related 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 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.
Refer Tasks
This category of tasks is also straight-forward. You'll use SipPhone.refer() to send an in-dialog or out-of-dialog REFER message and initiate a subscription. After that, you'll use the returned ReferSubscriber object methods to step through the REFER/NOTIFY and subsequent SUBSCRIBE/NOTIFY sequence(s) and to view the subscription status information which is maintained by the ReferSubscriber object.

As far as customizing the content of refer subscription messages sent by the test program side: you may vary the default (correct) content of outgoing SUBSCRIBE requests and NOTIFY responses or provide your own incorrect/malformed message (with the exception of the very first REFER 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 REFER request is needed to initialize the subscription properly. If you want to send out a bad initial REFER 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.

Event Subscription & Notification

This section describes the event subscription/notification capabilities implemented in SipUnit and how you can use them in your test program.

General

What Event Packages Does SipUnit Support?
SipUnit supports the following PRESENCE capabilities:
  • 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
  • presence fetch
  • "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.
SipUnit supports the following REFER capabilities:
  • outbound REFER handling, in-dialog or out-of-dialog
  • implicit subscription support including refresh, unsubscribe
  • "message/sipfrag" NOTIFY body format
  • validation per RFC of received REFER/SUBSCRIBE responses, NOTIFY requests
  • authentication challenge handling for sent REFERs and SUBSCRIBEs (DIGEST method only)
  • user control over the event "id" and other information sent in a given REFER/SUBSCRIBE request or NOTIFY response.
What RFCs Does SipUnit Implement?
Pertinent RFCs that SipUnit follows with respect to "Subscriber"-side presence and refer event package handling (see limitations next section):
  • RFC-3261 - SIP: Session Initiation Protocol
  • RFC-3265 - Session Initiation Protocol (SIP)-Specific Event Notification
  • RFC-3856  - A Presence Event Package for the Session Initiation Protocol (SIP)
  • RFC-3863 - Presence Information Data Format (PIDF)
  • RFC-3515 - The Session Initiation Protocol (SIP) Refer Method
What are the Limitations? What Doesn't It Do?
Currently the following is NOT supported:
  • the NOTIFY-sender side, officially (although there is an utility/simulator for both presence and refer, for test purposes)
  • multiple subscriptions in the same dialog
  • automatic refresh/keep alive of subscriptions
  • sending authentication challenge for received NOTIFY messages
  • extensions and mustUnderstands in the presence NOTIFY body (currently ignored)
  • PUBLISH method at a high level (can be done using low-level SipSession methods to send a request)
  • presence URI format
  • TLS, S/MIME.
More detail on the SipUnit presence and refer support follows.

Presence

What Type of Presence Does SipUnit Support? What Types Are There?
A test program using the primary SipUnit presence capabilities (SipPhone buddy methods and PresenceSubscriber 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 so far.
 

A test program using the primary SipUnit presence capabilities (SipPhone buddy methods, PresenceSubscriber 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.). However, the PresenceNotifySender doesn't validate messages it receives - your test program will have to do that if you use it. There are numerous presence tests in the examples directory that use it so look there if you need an example.

A test class method using both the primary SipUnit presence capabilities
(SipPhone buddy methods and PresenceSubscriber 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.

Overview of Operation
Here's how it works (see accompanying diagram below):
  1. Just like a normal call processing test, your presence test class creates one or more SipPhone objects. SipPhone supports buddy list operations and single-shot fetches in addition to its call processing capabilities. If authentication will be required, give the necessary credentials to the SipPhone by calling addUpdateCredential().
  2. Next, call addBuddy() or fetchPresenceInfo() on the SipPhone. This causes a SUBSCRIBE message to be created and sent to the server/target, and your test program is blocked on the method call until a first response is received. Assuming a favorable response is received (as opposed to a fatal error response), the SipPhone method returns a PresenceSubscriber object that will represent this buddy/subscription for the life of the subscription.
  3. At this point, you'll now use methods of the returned PresenceSubscriber 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 processResponse() 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, collecting and validating message(s) received from the Far End. When the SUBSCRIBE transaction is over, processResponse() returns a success/fail indication to your test program. Assuming success, you can proceed by checking resulting info as shown in the diagram below.
  4. 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 PresenceSubscriber 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 modify/corrupt it if you want to use the JAIN SIP API  to do it. Regardless, you can see the NOTIFY processing results by calling the getXyz() methods on the PresenceSubscriber object as shown in the diagram below.
  5. 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 the 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() call on the PresenceSubscriber object works just the same way, except a new PresenceSubscriber object isn't created (the existing one continues to be used).


image not available

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.

Refer

Implementation
A test program using the primary SipUnit refer capabilities (SipPhone.refer() and ReferSubscriber class methods) can act as a "Subscriber" side User Agent. That is, it can send REFER and SUBSCRIBE messages and receive/validate NOTIFY messages. Your test program can control the contents of outgoing REFER/SUBSCRIBE requests and NOTIFY responses. By default, the messages are correctly formed and the content is valid.
 

A test program using the primary SipUnit refer capabilities (SipPhone.refer() and ReferSubscriber class methods) does not receive REFER/SUBSCRIBE messages or send NOTIFY messages. It cannot be used to act as the "Notifier" side User Agent.

A test program using the SipUnit ReferNotifySender utility class can simulate the refer-ee (Notifier-side) UA for the purpose of testing a referrer (Subscriber-side) UA. That is, it can receive REFER/SUBSCRIBE messages and send NOTIFY messages. Your test program tells it the values to send in REFER/SUBSCRIBE responses (status code, reason phrase) and NOTIFY requests (subscription state, etc.). However, the
ReferNotifySender doesn't validate messages it receives - your test program will have to do that if you use it. There are numerous refer tests in the examples directory that use it so look there if you need an example.

A test class method using both the primary SipUnit refer capabilities
(SipPhone.refer() and ReferSubscriber class methods) for one side of a subscription (the referrer "Subscriber") and the ReferNotifySender utility class for the other side of the subscription (the refer-ee "Notifier") can be used to test/generate REFER/SUBSCRIBE/NOTIFY traffic through network elements such as a proxy.
Overview of Operation
Here's how it works (see accompanying diagram below):
  1. Just like a normal call processing test, your refer test class creates one or more SipPhone objects. If authentication will be required, give the necessary credentials to the SipPhone by calling addUpdateCredential().
  2. You can use the convenience method SipPhone.getUri() to prepare a 'referTo' SipURI based on method, join, replaces, etc.
  3. Next, call refer() on the SipPhone. There are multiple versions of this method that support in-dialog or out-of-dialog REFER. This causes a REFER message to be created and sent to the Far End, and your test program is blocked on the method call until a first response is received. Assuming a favorable response is received (as opposed to a fatal error response), the SipPhone method returns a ReferSubscriber object that will represent the implicit subscription for the life of the subscription.
  4. At this point, you'll now use methods of the returned ReferSubscriber class to step through the remainder of this REFER/NOTIFY sequence. (Note, you may also be doing other things with the SipPhone or with its buddy list as well, besides this particular sequence). You may check the status code of the initial response that was received before calling processResponse() which will complete the REFER transaction handling. This is another blocking method that processes the response just received and handles the remainder of the REFER operation, collecting and validating message(s) received from the Far End. When the REFER transaction is over, processResponse() returns a success/fail indication to your test program. Assuming success, you can proceed by checking resulting info as shown in the diagram below.
  5. 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 REFER 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 ReferSubscriber object with the received information (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 modify/corrupt it if you want to use the JAIN SIP API  to do it. Regardless, you can see the NOTIFY processing results by calling the getXyz() methods on the ReferSubscriber object as shown in the diagram below.
  6. 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 the REFER/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 refresh() and unsubscribe() call on the ReferSubscriber object works just the same way, except a SUBSCRIBE is sent instead of REFER, and a new ReferSubscriber object isn't created (the existing one continues to be used).

image not available

Where's An Example I Can Look At?
For an example of test code using the REFER, click here. Also look in the examples directory of the download package for more examples.

How to Get the JAIN SIP Objects (Transaction, Dialog) From SipUnit Classes

The way to get to the JAIN SIP objects depends on which SipUnit objects you're using and the message send/receive context:

If you're using the below and you are:  --->
Sending a request
Receiving a response
Receiving a request
Sending a response
Low level SipSession methods
Use the SipTransaction returned by sendRequestWithTransaction()
See left, or use the EventObject returned by waitResponse() when the response comes in
Use the RequestEvent returned by waitRequest(). For a dialog-creating request, some JAIN SIP info isn't available until a response is sent, see right.
See left, or use the SipTransaction returned by sendReply()
High level call processing objects (SipPhone, SipCall)
After calling a SipPhone or SipCall INVITE-sending non-blocking method, before a response is received you can call SipCall.getLastTransaction(). Once an initial response has been received, you can call SipCall.getDialog(). There's also a SipCall.getDialogId() if that's all you need. Use SipCall.getLastReceivedResponse() or SipCall.getDialog(). There's also a SipCall.getDialogId() if that's all you need.
Use SipCall.getLastReceivedRequest() unless the dialog has already  been established in which case you can call SipCall.getDialog(). There's also a SipCall.getDialogId() if that's all you need. See left. Once you've sent an initial response you can call SipCall.getDialog(). There's also a SipCall.getDialogId() if that's all you need.
High level subscription objects (PresnceSubscriber, ReferSubscriber)
After calling a SUBSCRIBE/REFER-sending method (ex: SipPhone.addBuddy()/refer()), call getLastReceivedResponse() or getDialog() on the returned subscription object. It also has a getDialogId() if that's all you need.
Call getLastReceivedResponse() or getDialog() on the subscription object. It also has a getDialogId() if that's all you need. Use the RequestEvent returned by waitNotify() or call getLastReceivedRequest() or getDialog() on the subscription object. It also has a getDialogId() if that's all you need. See left

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\n");

            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. Please read the Event Subscription (Presence) section of this document that explains the procedure in detail. The main methods your test uses to step through the SUBSCRIBE/NOTIFY sequence are highlighted 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. Also, this test contains numerous asserts to verify the SipUnit API handling which your test wouldn't need. But, your test will use the same principles you see below:
  • you'll use a SipPhone object ("ua" below) that will have a buddy list or can do a fetch (each buddy/fetch has a PresenceSubscriber object respresenting it);
  • you'll call a method on the SipPhone (such as addBuddy()) which will initiate a SUBSCRIBE/NOTIFY sequence to your server/application;
  • you'll use methods of the returned PresenceSubscriber object 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.

    /*
     * @see SipTestCase#setUp()
     */
    public void setUp() throws Exception
    {
        try
        {
            sipStack = new SipStack(testProtocol, myPort, properties);
            SipStack.setTraceEnabled(properties.getProperty("sipunit.trace")
                    .equalsIgnoreCase("true")
                    || properties.getProperty("sipunit.trace")
                            .equalsIgnoreCase("on"));
        }
        catch (Exception ex)
        {
            fail("Exception: " + ex.getClass().getName() + ": "
                    + ex.getMessage());
            throw ex;
        }

        try
        {
            ua = sipStack.createSipPhone("sip:amit@nist.gov");
        }
        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()
    {
        String buddy = "sip:becky@cafesip.org"; // I am amit

        try
        {
            // create far end (presence server simulator, fictitious buddy)
            PresenceNotifySender ub = new PresenceNotifySender(sipStack
                    .createSipPhone(host, testProtocol, myPort, buddy));

            // prepare far end to receive SUBSCRIBE
            assertTrue(ub.processSubscribe(5000, SipResponse.OK, "OK"));
            Thread.sleep(500);

            // do the SUBSCRIBE sequence
            PresenceSubscriber s = ua.addBuddy(buddy, 2000);
            assertNotNull(s);
            assertTrue(s.processResponse(1000));
            assertTrue(s.isSubscriptionActive());

            // do the NOTIFY sequence
            String notify_body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@cafesip.org\" xmlns=\"urn:ietf:params:xml:ns:pidf\"><tuple id=\"1\"><status><basic>closed</basic></status></tuple></presence>";
            assertTrue(ub.sendNotify(SubscriptionStateHeader.ACTIVE, null,
                    notify_body, 2400, false));
            RequestEvent reqevent = s.waitNotify(1000);
            assertNotNull(reqevent);
            assertNoSubscriptionErrors(s);
            Response response = s.processNotify(reqevent);
            assertNotNull(response);
            assertTrue(s.isSubscriptionActive());

            // check PRESENCE info
            HashMap<String, PresenceDeviceInfo> devices = s
                    .getPresenceDevices();
            assertEquals(1, devices.size());
            PresenceDeviceInfo dev = 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));

            // check buddy lists
            assertEquals(1, ua.getBuddyList().size());
            assertEquals(0, ua.getRetiredBuddies().size());
            assertNoSubscriptionErrors(s);

            // Now, end the subscription from our side

            // prepare far end to receive SUBSCRIBE
            assertTrue(ub.processSubscribe(5000, SipResponse.OK, "OK Ended"));
            Thread.sleep(500);

            // remove buddy from contacts, do the SUBSCRIBE sequence
            assertTrue(s.removeBuddy(300));
            assertFalse(s.isRemovalComplete());

            // check immediate impacts - buddy lists, subscription state
            assertEquals(0, ua.getBuddyList().size());
            assertEquals(1, ua.getRetiredBuddies().size());
            assertNoSubscriptionErrors(s);
            assertNotNull(ua.getBuddyInfo(buddy)); // can still be found
            assertEquals(s.getTargetUri(), ua.getBuddyInfo(buddy)
                    .getTargetUri());
            assertFalse(s.isSubscriptionActive());
            assertFalse(s.isSubscriptionPending());
            assertTrue(s.isSubscriptionTerminated());
            String reason = s.getTerminationReason();
            assertNotNull(reason);

            // check the return info
            assertEquals(SipResponse.OK, s.getReturnCode());
            ResponseEvent resp_event = s.getCurrentResponse();
            response = resp_event.getResponse();
            assertEquals("OK Ended", response.getReasonPhrase());
            assertEquals(0, response.getExpires().getExpires());
            assertEquals(response.toString(), s.getLastReceivedResponse()
                    .getMessage().toString());
            ArrayList<SipResponse> received_responses = s
                    .getAllReceivedResponses();
            assertEquals(2, received_responses.size());
            assertEquals(response.toString(), received_responses.get(1)
                    .toString());

            // process the received response
            assertTrue(s.processResponse(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());
            assertNoSubscriptionErrors(s);

            // tell far end to send a NOTIFY
            notify_body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@cafesip.org\" xmlns=\"urn:ietf:params:xml:ns:pidf\"> <tuple id=\"3\"> <status> <basic>open</basic> </status> </tuple> </presence>";
            assertTrue(ub.sendNotify(SubscriptionStateHeader.TERMINATED,
                    "done", notify_body, 0, false));

            // get the NOTIFY
            reqevent = s.waitNotify(1000);
            assertNotNull(reqevent);
            assertNoSubscriptionErrors(s);

            // process the NOTIFY
            response = s.processNotify(reqevent);
            assertNotNull(response);
            assertEquals(0, ua.getBuddyList().size());
            assertEquals(1, ua.getRetiredBuddies().size());
            assertNoSubscriptionErrors(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 - devices/tuples
            // -----------------------------------------------
            devices = s.getPresenceDevices();
            assertEquals(1, devices.size());
            dev = 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());

            // reply to the NOTIFY
            assertTrue(s.replyToNotify(reqevent, response));

        }
        catch (Exception e)
        {
            e.printStackTrace();
            fail("Exception: " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

A REFER Example Using REFER/NOTIFY, SUBSCRIBE/NOTIFY (refresh subscription)

This example shows a complete test method that sends out an in-dialog REFER message (from the called side), establishing a subscription via the REFER/NOTIFY sequence and then refreshes the subscription via a SUBSCRIBE/NOTIFY sequence. Please read the Event Subscription (Refer) section of this document that explains the procedure in detail. The main methods your test uses to step through the REFER/NOTIFY and SUBSCRIBE/NOTIFY sequences are highlighted in bold.

NOTE: this test was written to validate the SipUnit API itself (referrer client-side handling, which in this example is the called party of an established session) - it uses a utility class, ReferNotifySender, at the calling side to simulate the refer-ee. Your test method wouldn't need that (simulator) side of it since your test target would act as the calling ("ua") side of this example. That is, in this scenario your test target would respond to the REFER/SUBSCRIBE messages sent out by this test, send NOTIFY messages to this test, and this test would just have the code for the called/referrer side ("ub" below). Also, this test contains extra asserts to verify the SipUnit API handling which your test wouldn't need. But, your test will use the same principles you see below:
  • you'll use a SipPhone object ("ub" in the example below) as the referrer;
  • you'll call a method on the SipPhone (refer()) which will initiate a REFER/NOTIFY sequence with your server/application;
  • you'll use methods of the returned ReferSubscriber object to step through the REFER/NOTIFY sequence and validate the messages sent by your server/application.
  • you'll use the methods of the ReferSubscriber to initiate a refresh (as in the example below) or unsubscribe sequence - IE, send SUBSCRIBE, receive NOTIFY.

private SipStack sipStack;
private SipPhone ua;
etc.

    /*
     * @see SipTestCase#setUp()
     */
    public void setUp() throws Exception
    {
        try
        {
            sipStack = new SipStack(testProtocol, myPort, properties);
            SipStack.setTraceEnabled(properties.getProperty("sipunit.trace")
                    .equalsIgnoreCase("true")
                    || properties.getProperty("sipunit.trace")
                            .equalsIgnoreCase("on"));
        }
        catch (Exception ex)
        {
            fail("Exception: " + ex.getClass().getName() + ": "
                    + ex.getMessage());
            throw ex;
        }

        try
        {
            ua = sipStack.createSipPhone("sip:amit@nist.gov");
        }
        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 testOutboundIndialogBrefersAwithRefresh() throws Exception
    {
        // A calls B, call established
        // B sends in-dialog REFER to A, gets 202 Accepted
        // A sends state-active NOTIFY to B, gets OK in response
        // B refreshes the subscription
        // A sends subscription-terminating NOTIFY to B, gets OK in response

        // create and set up the B party
        SipPhone ub = sipStack.createSipPhone("sip:becky@cafesip.org");
        SipCall b = ub.createSipCall();
        assertTrue(b.listenForIncomingCall());

        // make the call from A
        SipCall a = ua.makeCall("sip:becky@cafesip.org", properties
                .getProperty("javax.sip.IP_ADDRESS")
                + ':' + myPort + '/' + testProtocol);
        assertLastOperationSuccess(ua.format(), ua);

        // B side answer the call
        assertTrue(b.waitForIncomingCall(1000));
        assertTrue(b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0));
        Thread.sleep(20);
        assertTrue(b.sendIncomingCallResponse(Response.OK,
                "Answer - Hello world", 0));
        Thread.sleep(200);

        // A side finish call establishment
        assertAnswered("Outgoing call leg not answered", a);
        a.sendInviteOkAck();
        assertLastOperationSuccess("Failure sending ACK - " + a.format(), a);
        Thread.sleep(1000);

        // B sends in-dialog REFER to A, gets 202 Accepted
        // ****************************************************************
        // A side - prepare to receive REFER
        ReferNotifySender referHandler = new ReferNotifySender(ua);
        referHandler.setDialog(a.getDialog());
        assertTrue(referHandler.processRefer(4000, SipResponse.ACCEPTED,
                "Accepted"));

        // B side - create referTo URI and send a REFER message
        SipURI referTo = ub.getUri("sip:", "dave@denver.example.org", "udp",
                "INVITE", null, null, null, null, null);

        ReferSubscriber subscription = ub.refer(b.getDialog(), referTo,
                "myeventid", 4000);
        if (subscription == null)
        {
            fail(ub.getReturnCode() + ':' + ub.getErrorMessage());
        }

        // A side - verify received REFER contents
        RequestEvent requestEvent = referHandler.getLastReceivedRequest()
                .getRequestEvent();
        assertNotNull(requestEvent);
        Request req = requestEvent.getRequest();
        assertEquals(SipRequest.REFER, req.getMethod());
        assertEquals(a.getDialogId(), requestEvent.getDialog().getDialogId());
        assertEquals("myeventid", ((EventHeader) req
                .getHeader(EventHeader.NAME)).getEventId());

        // B side - check the initial results
        assertEquals(SipResponse.ACCEPTED, subscription.getReturnCode());
        assertTrue(subscription.isSubscriptionPending());
        assertEquals(1, ub.getRefererList().size());
        assertEquals(subscription, ub.getRefererInfoByDialog(b.getDialogId())
                .get(0));

        // B side - process the received response
        assertTrue(subscription.processResponse(1000));
        assertNoSubscriptionErrors(subscription);

        // B side - check the response processing results
        assertTrue(subscription.isSubscriptionPending());
        assertNull(subscription.getTerminationReason());
        assertEquals(0, subscription.getTimeLeft());

        // A sends state-active NOTIFY to B, gets OK in response
        // **************************************************************
        // A side - send a NOTIFY
        Thread.sleep(20);
        Request notifyRequest = a.getDialog().createRequest(SipRequest.NOTIFY);
        notifyRequest = referHandler.addNotifyHeaders(notifyRequest, null,
                null, SubscriptionStateHeader.ACTIVE, null,
                "SIP/2.0 100 Trying\n", 60);
        SipTransaction trans = referHandler.sendStatefulNotify(notifyRequest,
                false);
        assertNotNull(trans);

        // B side - wait for the NOTIFY
        RequestEvent reqevent = subscription.waitNotify(1000);
        assertNotNull(reqevent);

        // B side - examine the NOTIFY request object, verify subscription
        // message getters
        Request request = reqevent.getRequest();
        assertEquals(60, ((SubscriptionStateHeader) request
                .getHeader(SubscriptionStateHeader.NAME)).getExpires());
        SipRequest sipreq = subscription.getLastReceivedRequest();
        assertBodyContains(sipreq, "SIP/2.0 100 Trying");
        assertHeaderContains(sipreq, EventHeader.NAME, "myeventid");

        // B side - process the NOTIFY
        resp = subscription.processNotify(reqevent);
        assertNotNull(resp);
        assertNoSubscriptionErrors(subscription);

        // B side - check the NOTIFY processing results on subscription
        assertTrue(subscription.isSubscriptionActive());
        assertTrue(subscription.getTimeLeft() <= 60
                && subscription.getTimeLeft() > 55);

        // B side - check the NOTIFY response that was created
        assertEquals(SipResponse.OK, resp.getStatusCode());
        assertEquals(SipResponse.OK, subscription.getReturnCode());

        // B side - reply to the NOTIFY
        assertTrue(subscription.replyToNotify(reqevent, resp));

        // A side - verify the NOTIFY response got sent by B
        Object obj = referHandler.waitResponse(trans, 10000);
        assertNotNull(obj);
        assertTrue(obj instanceof ResponseEvent);
        assertEquals(SipResponse.OK, ((ResponseEvent) obj).getResponse()
                .getStatusCode());

        // B refreshes the subscription
        // **************************************************************
        // prepare A to receive SUBSCRIBE
        assertTrue(referHandler.processSubscribe(2000, SipResponse.OK, "OK"));
        // refresh
        assertTrue(subscription.refresh(10, "eventid-x", 500));
        assertEquals(SipResponse.OK, subscription.getReturnCode());
       assertEquals("eventid-x", ((EventHeader) subscription
                .getLastSentRequest().getHeader(EventHeader.NAME)).getEventId());
        assertTrue(subscription.processResponse(200));
        assertTrue(subscription.isSubscriptionActive());
        assertTrue(subscription.getTimeLeft() <= 10
                && subscription.getTimeLeft() > 5);

        // A sends subscription-terminating NOTIFY to B, gets OK in response
        // **************************************************************
        // A side - send a NOTIFY
        Thread.sleep(20);
        notifyRequest = a.getDialog().createRequest(SipRequest.NOTIFY);
        notifyRequest = referHandler.addNotifyHeaders(notifyRequest, null,
                null, SubscriptionStateHeader.TERMINATED, "noresource",
                "SIP/2.0 100 Trying\n", 0);
        trans = referHandler.sendStatefulNotify(notifyRequest, false);
        assertNotNull(trans);

        // B side - wait for the NOTIFY
        reqevent = subscription.waitNotify(1000);
        assertNotNull(reqevent);

        // B side - examine the NOTIFY request object, verify subscription
        // message getters
        request = reqevent.getRequest();
        assertTrue(((SubscriptionStateHeader) request
                .getHeader(SubscriptionStateHeader.NAME)).getExpires() < 1);
        sipreq = subscription.getLastReceivedRequest();
        assertTrue(sipreq.isNotify());
        assertBodyContains(sipreq, "SIP/2.0 100 Trying");

        // B side - process the NOTIFY
        resp = subscription.processNotify(reqevent);
        assertNotNull(resp);
        assertNoSubscriptionErrors(subscription);

        // B side - check the NOTIFY processing results on subscription
        assertTrue(subscription.isSubscriptionTerminated());
        assertEquals("noresource", subscription.getTerminationReason());

        // B side - check the NOTIFY response that was created
        assertEquals(SipResponse.OK, resp.getStatusCode());
        assertTrue(resp.getReasonPhrase().equals("OK"));
        assertEquals(SipResponse.OK, subscription.getReturnCode());

        // B side - reply to the NOTIFY
        assertTrue(subscription.replyToNotify(reqevent, resp));

        // A side - verify the NOTIFY response got sent by B
        obj = referHandler.waitResponse(trans, 10000);
        assertNotNull(obj);
        assertTrue(obj instanceof ResponseEvent);
        assertEquals(SipResponse.OK, ((ResponseEvent) obj).getResponse()
                .getStatusCode());

    }

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 JAIN-SIP project. That jar is already in the sipunit/lib dir.  The tool 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 take a 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 free, open source servers available. Check out cafesip's SipExchange server. Also see the jain-sip-presence-proxy project page. 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.

Special thanks to external contributors to SipUnit including:
  • Alcatel-Lucent - for adding CANCEL support.
  • M. Ranganathan - for miscellaneous bug fixes and improvements.
  • manchi - for notes on STUN support and some example code.
  • Venkita S. - for changes to SipSession and SipStack for creating multiple SIP stacks independent of IP address
  • D. Shutt - for the most requirements and feedback we've gotten from someone.


Search this website 


Send questions or comments about this web site to webmaster@cafesip.org.
Copyright © 2005, CafeSip.org.
Licensed under the CafeSip License.
The CafeSip.org projects, web site and web facilities have been sponsored by QUIK Computing

Top of page