,

Code-Driven Location Simulation

The Windows Phone emulator’s location simulator is useful for testing your app while it is executing within the emulator. To test the geo location features of your app on a physical device or within a unit test requires a different code-driven approach.

This section demonstrates how to abstract the use of the Geolocator class so that it can be replaced by a testable mock class.

On first glance you might think that mocking the SDK’s geo location API should be a simple affair. Many of the classes in the API are, however, sealed—including the Geolocator class. This makes mocking them awkward because they must be wrapped or accessed via custom proxy classes.

The Geolocator class implements an internal IGeolocator interface, which would make an excellent candidate for abstracting the duties of the Geolocator class. Unfortunately, IGeolocator is marked internal, putting it out of our reach.

So the downloadable sample code includes a custom IGeoLocator interface, which is almost identical to the SDK’s IGeolocator interface (see Listing 17.2).

LISTING 17.2. IGeoLocator Interface


public interface IGeoLocator
{
    event EventHandler<PositionChangedProxyEventArgs> PositionChanged;
    event EventHandler<StatusChangedProxyEventArgs> StatusChanged;

    Task<GeopositionWrapper> GetGeoCoordinateAsync();

    Task<GeopositionWrapper> GetGeoCoordinateAsync(
        TimeSpan maximumAge, TimeSpan timeout);

    PositionAccuracy DesiredAccuracy { get; set; }
    PositionStatus LocationStatus { get; }
    double MovementThreshold { get; set; }
    uint ReportInterval { get; set; }

    void Start();
    void Stop();
}


A custom GeolocatorProxy class wraps the built-in Geolocator object. GeolocatorProxy implements the IGeoLocator interface (see Listing 17.3). GeolocatorProxy is designed to be used in place of the Geolocator class. It offers all the features of the built-in Geolocator, but can be swapped out during testing.

Although the built-in Geolocator class does not include methods for starting and stopping location tracking, implementations of the custom IGeoLocator interface do. GeolocatorProxy subscribes to its Geolocator events in the Start method and unsubscribes in the Stop method.

The PositionChanged and StatusChanged events of the built-in Geolocator class use a TypedEventHandler<T> as the delegate type rather than the familiar EventHandler. The appearance of the TypedEventHandler is new to the Windows Phone 8 SDK and several APIs, including the geo location API, make use of it. This proves troublesome when working with Reactive Extensions (Rx). Thus, the GeolocatorProxy exposes the events using the nongeneric EventHandler class. You look at the Rx later in this chapter.

One last obstacle to abstracting the geo location API is that some of the types in the SDK’s geo location API do not provide the means for setting their members. These classes include, in particular, the PositionChangedEventArgs, StatusChangedEventArgs, and the Geoposition classes, which have been replaced with more flexible custom types to accommodate our mocking framework.

LISTING 17.3. GeolocatorProxy Class (Excerpt)


public sealed class GeolocatorProxy : IGeoLocator
{
    readonly Geolocator geolocator = new Geolocator();
...
    public uint ReportInterval
    {
        get
        {
            return geolocator.ReportInterval;
        }
        set
        {
            geolocator.ReportInterval = value;
        }
    }

    public void Start()
    {
        Stop();

        geolocator.PositionChanged += HandlePositionChanged;
        geolocator.StatusChanged += HandleStatusChanged;
    }

    public void Stop()
    {
        geolocator.PositionChanged -= HandlePositionChanged;
        geolocator.StatusChanged -= HandleStatusChanged;
    }

    public event EventHandler<PositionChangedProxyEventArgs> PositionChanged;

    void OnPositionChanged(PositionChangedProxyEventArgs e)
    {
        PositionChanged.Raise(this, e);
    }

    void HandlePositionChanged(Geolocator sender, PositionChangedEventArgs args)
    {
        OnPositionChanged(new PositionChangedProxyEventArgs(
                        new GeopositionWrapper(args.Position)));
    }
...
}


The custom MockGeoLocator mimics the behavior of the built-in Geolocator and allows you to supply a list of GeoCoordinate values that represent a predefined route and are analogous to the location simulator’s map points.

In addition to the properties of the IGeoLocator interface, MockGeoLocator has the following three properties that allow you to tailor when the PositionChanged event is raised:

Image InitializationMsThe time, in milliseconds, before transitioning the Status to the NoData state

Image FixDelayMsThe time before the transitioning the Status from the NoData state to the Ready state

Image PositionChangedDelayMsThe time interval, in milliseconds, between PositionChanged events

MockGeoLocator allows you to add waypoints to a list of GeoCoordinate values using its WayPoints property.

The MockGeoLocator.WalkPath method runs on a background thread and processes the waypoints sequentially (see Listing 17.4).

If there are no waypoints specified, the MockGeoLocator uses a default list of coordinates.

The first time WalkPath is called, the class simulates initialization by pausing for the duration specified by the InitializationMs value. It then updates its Status to Ready and iterates over the WayPoints collection, raising the PositionChanged event for each one.

LISTING 17.4. MockGeoLocator.WalkPath Method


void WalkPath()
{
    if (wayPoints.Count < 1)
    {
        List<GeoCoordinate> defaultWayPoints = GetDefaultCoordinates();
        wayPoints.AddRange(defaultWayPoints);
    }

    if (firstRun)
    {
        Status = PositionStatus.Initializing;
        Wait(InitializationMs);
    }

    Status = PositionStatus.NoData;
    Wait(FixDelayMs);

    GeoCoordinate coordinate = wayPoints.First();

    if (firstRun)
    {
        Position = new Position<GeoCoordinate>(
                            DateTimeOffset.Now, coordinate);
        firstRun = false;
    }
    Status = PositionStatus.Ready;
    Wait(PositionChangeDelayMs);
    int index = 1;

    while (started)
    {
        if (wayPoints != null && wayPoints.Count > index)
        {
            coordinate = wayPoints[index++];
        }
        SetPosition(coordinate);
        Wait(PositionChangeDelayMs);
    }
}


The Wait method uses the Monitor to block the background thread for the specified number of milliseconds (see Listing 17.5).


Tip

Using the Monitor to block the thread is preferable to calling Thread.Sleep because, unlike Monitor.Wait, Thread.Sleep cannot be unblocked.


LISTING 17.5. Wait Method


static readonly object waitLock = new object();

static void Wait(int delayMs)
{
    if (delayMs > 0)
    {
        lock (waitLock)
        {
            Monitor.Wait(waitLock, delayMs);
        }
    }
}


Both MockGeoLocator and the GeolocatorProxy implement IGeoLocator. This allows you to use either type without referring to a concrete implementation, which is demonstrated in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.49.108