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.

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
  • 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.
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)
  • presence URI format
  • TLS, S/MIME.

Overview of Operation

Here's how it works:
  1. 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().
  2. 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.
  3. 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.
  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 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.
  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 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.


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