Skip to content

E2E testing

Emia edited this page Jun 11, 2024 · 5 revisions

Writing E2E/Integration tests for Voip/SIP in GO

Writing automated E2E tests for calls can be chalenging with SIP. Instrumenting some softphones is also not best deal as we do not have full control or hard to extend.

Using GO language and GO test framework with sipgo library makes this easier.

Show me the code!

Lets go!

E2E testing calls

sipgox is currently here to help

Lets say you want to test your routing,dialplan in some service like proxy(kamailio), pbx(asterisk). In a call we always have at least 2 parties, UAC and UAS, and It would be great if we can have a test that simulate both sides at the same time.

Lets start simple. UAC creating a Simple Call and checking response

import (
	"context"
	"testing"

	"github.com/emiago/sipgo"
	"github.com/emiago/sipgo/sip"
	"github.com/emiago/sipgox"
	"github.com/stretchr/testify/require"
)


func TestSimpleCall(t *testing.T) {
    ctx, _ := context.WithTimeout(ctx, 32*time.Second)

    // First create our user agent
    uac, _ := sipgo.NewUA(sipgo.WithUserAgent("TestUAC"))
    defer uac.Close()

    // Using sipgox attach user agent to Phone
    uacPhone := sipgox.NewPhone(ua)

    // Start dial on sip:[email protected]:5060
    // We use context to control duration of dialin
    recipient := sip.Uri{User: "123456789", Host: "asterisk.xy", Port: 5060},
    dialog, err := uacPhone.Dial(
        ctx,
        recipient,
        sipgox.DialOptions{},
    )
    require.NoError(t, err)
    t.Log("UAC answered")

    select {
    case <-dialog.Done():
        // Other side hanguped
    case <-time.After(3 * time.Second): // After 3 second we hangup
        dialog.Hangup(ctx)
    }
}

What if we want to test that we receive something other than 200 SIP OK This is passed as part of error. Here testing that we have Busy:

func TestBusyCall(t *testing.T) {
    // ...
    _, err := phone.Dial(...)
    e, ok := err.(*sipgox.DialResponseError)
    assert.True(t, ok)
    assert.Equal(t, sip.StatusBusyHere, e.StatusCode())
}

UAS and UAC in same test

Now lets add also receiving UAS side. How you automate that your service sends call to UAS is out of scope, but normally you can:

  • register UAS
  • use some sip headers
func TestCallBothParties(t *testing.T) {
    ctx, _ := context.WithTimeout(ctx, 32*time.Second)

    // First setup our UAS
      uas, _ := sipgo.NewUA(sipgo.WithUserAgent("TestUAS"))
    defer uas.Close()

    uasPhone := sipgox.NewPhone(usa,
        sipgox.WithPhoneListenAddr(sipgox.ListenAddr{
            Network: "udp",
            Addr:    "127.0.0.1:5088",
        }),
    )

    // Run UAS in background
    go func() {
        // Wait for answer
        dialog, err := uasPhone.Answer(ctx, sipgox.AnswerOptions{})
        require.NoError(t, err)

        // No error so we have answered a call
        // Lets check did we recevie some header
        hdr := dialog.InviteRequest.GetHeader("X-Test-Call")
        assert.NotEmpty(t, hdr)

        // Wait for call/dialog to be completed
        t.Log("UAS answered")
        <-dialog.Done()
        return
    }()

    // First create our user agent
    uac, _ := sipgo.NewUA(sipgo.WithUserAgent("TestUAC"))
    defer uac.Close()
    uacPhone := sipgox.NewPhone(ua)

    // Start dial on sip:[email protected]:5060
    recipient := sip.Uri{User: "123456789", Host: "asterisk.xy", Port: 5060},
    dialog, err := uacPhone.Dial(
        ctx,
        recipient,
        sipgox.DialOptions{
            SipHeaders: []sip.Header{
                sip.NewHeader("X-Test-Call", "true"), // Pass some custom sip header
            },
        },
    )
    require.NoError(t, err)
    t.Log("UAC answered")

    select {
    case <-dialog.Done():
        // Other side hanguped
    case <-time.After(3 * time.Second): // After 3 second we hangup
        dialog.Hangup(ctx)
    }
}

Media

if you are interested with dealing media more, then checkout media package for more. Some example of building echo client/server you have on sipgox

To be continued..