Click here to Skip to main content
Click here to Skip to main content
Go to top

Catel - Part 8 of n: WP7 Mango and Unit Testing the Camera

, 2 Sep 2011
Rate this:
Please Sign up or sign in to vote.
This article is about Windows Phone 7 Mango and unit testing the camera

Article browser

Table of Contents

  1. Introduction
  2. Demo application
  3. PART I - Unit testing on Windows Phone 7

  4. Setting up unit tests
  5. 4. Unit testing without mocking
  6. PART II - Camera Service

  7. 5. Basics of the Camera Service
  8. 6. Using the CameraService in the emulator
  9. 7. Using the CameraService in unit tests
  10. 8. Conclusion

1. Introduction

Welcome to part 7 of the articles series about Catel. If you haven’t read the previous article(s) of Catel yet, it is recommended that you do. They are numbered so finding them shouldn’t be too hard.

You might be thinking right now: why does this guy implement a CameraService? There is a beautiful API available using the PhotoCamera class. Remind yourself again why you even want to write your application in MVVM? Was it because it’s the buzz-word of the century, or… Oh yeah, now I remember, you want to be able to unit test all your view models. Now tell me, how is that possible if you instantiate a PhotoCamera object? And, for example, how are you going to support all the different kinds of cameras out there (ones that support all flash modes, some that don’t support all flash modes, etc).

This article will explain how the CameraService is created and more important, why it is created. The CameraService allows you to truly interact in an MVVM manner with the camera on Windows Phone 7 Mango devices. This article uses Catel, but the service can be used on its own if you want to.

This article is split up into several parts. The first part is about unit testing for Windows Phone 7 in general. The second part is about unit testing the camera service. Finally, the last part is about the conclusion et al.

2. Demo application

2.1. Functional requirements

image003.jpg

The demo application used in this article is very simple. The first screen allows someone to take a picture using the camera. If there are existing pictures for the application, the user can also “flick” to the right to navigate through the pictures.

In the pictures screen, it is possible to flick to the next image (right) or the previous image (left). When the first image is shown and the user flicks left, the main page should become visible. It should also be possible to delete image. When an image is deleted, it should always select the image at the left of the index of the image being deleted. If the selected image is the first and there are still images left, the next first image should be shown. If there are no images left after deleting an item, the application should navigate to the main page.

image004.jpg

2.2. Requirements for unit testing

Great, the functional requirements are known. Now let’s take a look at how to unit test this application. The following features should be unit tested. All tests are numbered so they can be referenced easily.

MainPage

  • [MP_01] Flicking to right should not be possible if there are no images
  • [MP_02] Flicking to right should be possible if there are images available
  • [MP_03] Flicking to right when taking a picture should not be possible
  • [MP_04] Clicking the view button should not be able when there are no images
  • [MP_05] Clicking the view button should be able when there no images
  • [MP_06] Navigation to about view should be possible at all times
  • [MP_07] Taking a picture should be possible
  • [MP_08] Taking a picture when already taking a picture should not be possible

PhotoView

  • [PV_01] Flicking to right should not be possible if there are not images at the right
  • [PV_02] Flicking to right at an image in the middle should load next image
  • [PV_03] Flicking to left at an image in the middle should load previous image
  • [PV_04] Flicking to left at first image should navigate to main page
  • [PV_05] Canceling a delete image command should not navigate away
  • [PV_06] Deleting an image should navigate to previous image
  • [PV_07] Deleting the first image when images are left should navigate to next image
  • [PV_08] Deleting the first image when no images are left should navigate to main page
  • [PV_09] Clicking the camera button should always navigate to the main page

The example code that ships with this article contains unit tests for all the situations above.

PART I Unit testing on Windows Phone 7

3. Setting up unit tests

If you already know how to set up unit tests for a Windows Phone 7 application, you can skip this chapter.

Setting up unit tests for Windows Phone 7 isn’t as easy as you would expect. There is no unit test framework available out of the box, nor are there templates that allow you to create a unit test project. Some people are trying to outsmart the system by running regular Silverlight unit tests. I think that is a very bad idea because then you won’t test the logic against the WP7 framework, but against the Silverlight framework which is different.

3.1. Downloading the right libraries

First of all, there are special libraries needed to enable unit testing for WP7. The ones I use are in the Windows Phone 7 toolkit, but also located in the lib folder of the demo project that ships with this article. The following two libraries are needed:

  • Microsoft.Silverlight.Testing.dll
  • Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight.dll

3.2. Creating the unit test project

Now we have the required libraries, let’s go and create the unit test project. The first step is to create a new Windows Phone 7 application:

image004.jpg

Since we are using Mango, make sure to select Windows Phone 7.1. After this step, make sure to add references to both your original Windows Phone 7 application and the test libraries you downloaded earlier in this chapter. Visual Studio might warn you that it might be unsafe to reference Silverlight assemblies, but you can ignore that.

3.3. Modifying the MainPage

Last thing to do is to modify the MainPage.xaml.cs which is automatically by the project template. Make sure the constructor of the MainPage looks like the code below:

public MainPage()
{
    InitializeComponent();
 
    Content = UnitTestSystem.CreateTestPage();
}

You are now ready to create your first unit test! Let’s do that to make sure your unit tests are able to run. Add a new class called DemoTest and use the following content:

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class DemoTest
{
    [TestMethod]
    public void MyFirstUnitTest()
    {
        Assert.Inconclusive("We need to write our first unit test");
    }
}

Finally if you run the unit test project, you will see the following screens (which should be familiar if you are used to writing unit tests in Silverlight):

image006.jpg image007.jpg

4. Unit testing without mocking

In unit tests for WPF and Silverlight, you are probably used to mock all your interfaces. In Catel, we always provide a test implementation of every service so you are not forced to mock the services at all times. Of course it is still possible to mock services for WPF and Silverlight, but for Windows Phone 7 it’s a different kind of story.

There are currently no mocking frameworks available for WP7 and there will probably never going to be mocking frameworks. You are probably thinking? Why not, there is a big market out there and I want to be able to use unit tests for my WP7 applications. Unfortunately, the Reflection.Emit method is missing in WP7. This method is necessary to mock any objects.

4.1. Testing events

The first issue that came up while writing the code for this article was that the GestureListener had to be declared inside code. If you are unknown with the GestureListener, it’s a class that allows you to subscribe to specific gestures on a WP7 page. So, I created a command on the view model that should be executed when a flick gesture was detected. This is the way I keep the MVVM pattern intact:

var gestureListener = GestureService.GetGestureListener(this);
gestureListener.Flick += (sender, e) =>
{
    if (ViewModel != null)
    {
        ViewModel.Flick.Execute(e);
    }
};

As you can see, the Flick command is executed as soon as a Flick gesture is detected.

The next issue that came up was the need to unit test whether flicking left or right was that the constructor of the FlickGestureEventArgs was internal. Why the development team of the toolkit decided to make it internal is still unknown to me, but I had to find way around this.

The idea I came up with was pretty easy. In the application, I created a new object FlickData with exactly the same properties as the FlickGestureEventArgs. Then I created several constructors that allow easy unit testing and can also be used inside the real application logic. Below is the code for the FlickData class:

/// <summary>
/// Flick movements, easy to emulate flick gestures for testing purposes.
/// </summary>
public enum FlickMovement
{
    /// <summary>
    /// Left to right.
    /// </summary>
    LeftToRight,
 
    /// <summary>
    /// Right to left.
    /// </summary>
    RightToLeft
}
 
/// <summary>
/// Custom implementation for flick data because it's impossible to instantiate 
/// the <see cref="FlickGestureEventArgs"/> class.
/// </summary>
public class FlickData
{
    /// <summary>
    /// Initializes a new instance of the <see cref="FlickData"/> class.
    /// </summary>
    /// <param name="eventArgs">The <see cref="Microsoft.Phone.Controls.FlickGestureEventArgs"/> instance containing the event data.</param>
    public FlickData(FlickGestureEventArgs eventArgs)
        : this(eventArgs.Angle, eventArgs.Direction, eventArgs.HorizontalVelocity, eventArgs.VerticalVelocity) { }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="FlickData"/> class.
    /// </summary>
    /// <param name="angle">The angle.</param>
    /// <param name="direction">The direction.</param>
    /// <param name="horizontalVelocity">The horizontal velocity.</param>
    /// <param name="verticalVelocity">The vertical velocity.</param>
    public FlickData(double angle, Orientation direction, double horizontalVelocity, double verticalVelocity)
    {
        Angle = angle;
        Direction = direction;
        HorizontalVelocity = horizontalVelocity;
        VerticalVelocity = verticalVelocity;
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="FlickData"/> class.
    /// </summary>
    /// <param name="movement">The movement.</param>
    /// <remarks>
    /// This method should only be used for test purposes.
    /// </remarks>
    public FlickData(FlickMovement movement)
    {
        Angle = 0;
        Direction = Orientation.Horizontal;
        VerticalVelocity = 0;
 
        switch (movement)
        {
            case FlickMovement.LeftToRight:
                HorizontalVelocity = 15;
                break;
 
            case FlickMovement.RightToLeft:
                HorizontalVelocity = -15;
                break;
 
            default:
                throw new ArgumentOutOfRangeException("movement");
        }
    }
 
    /// <summary>
    /// Gets or sets the angle.
    /// </summary>
    /// <value>The angle.</value>
    public double Angle { get; set; }
 
    /// <summary>
    /// Gets or sets the direction.
    /// </summary>
    /// <value>The direction.</value>
    public Orientation Direction { get; set; }
 
    /// <summary>
    /// Gets or sets the horizontal velocity.
    /// </summary>
    /// <value>The horizontal velocity.</value>
    public double HorizontalVelocity { get; set; }
 
    /// <summary>
    /// Gets or sets the vertical velocity.
    /// </summary>
    /// <value>The vertical velocity.</value>
    public double VerticalVelocity { get; set; }
}

Beside this class, it is also required to update the subscription to the command (because it now cannot accept a parameter of type FlickGestureEventArgs, but a parameter of type FlickData). Because the FlickData class accepts an object of type FlickGestureEventArgs as argument, the code change is very small. This is the updated code:

var gestureListener = GestureService.GetGestureListener(this);
gestureListener.Flick += (sender, e) =>
{
    if (ViewModel != null)
    {
        ViewModel.Flick.Execute(new FlickData(e));
    }
};

Now everything is in place for the view model to be tested, let’s take a look at a simple unit test [MP_01] which tests if flicking is not allowed in the main page if no images are available:

[TestMethod]
public void Flick_NoImagesAvailable()
{
    var serviceLocator = ServiceLocator.Instance;
    var photoRepository = new TestPhotoRepository(0);
    serviceLocator.RegisterInstance<IPhotoRepository>(photoRepository);
 
    var vm = new MainPageViewModel();
 
    Assert.IsFalse(vm.Flick.CanExecute(new FlickData(FlickMovement.LeftToRight)), "Flick from left to right is never allowed");
    Assert.IsFalse(vm.Flick.CanExecute(new FlickData(FlickMovement.RightToLeft)));
}

As you can see, a test implementation of the IPhotoRepository is registered to make sure there are no images available in the view model. Then, the unit test checks whether a flick to left is not possible (a flick to left on the main page should never be possible) and whether a flick to right is not possible (a flick to right should not be possible if there are no images).

4.2. Testing services

When writing services for Catel, we as developers strongly believe in testability (otherwise, what’s the whole point of writing applications using MVVM?) of applications. Therefore we provide a test implementation of every service we write for the Catel MVVM toolkit. Some people find this quite an overhead because they love to use mocking frameworks. Whatever you use is fine with us, we just like to support both ways out of the box.

This principle seems to come out very handy for Windows Phone 7 since there are no mocking frameworks available in WP7. So, to test services provided by Catel is very, very simple. In this example, the IMessageService will be used so unit test can check whether the result is correctly handled.

First, let’s start with a unit test that makes any sense. In the demo application, it is possible to delete existing images. The user should be asked for confirmation so a unit test must be written to check if this confirmation is implemented correctly. The code for the whole unit test looks like this:

[TestMethod]
public void DeleteImage_Cancel()
{
    var serviceLocator = ServiceLocator.Instance;
    var photoRepository = new TestPhotoRepository(3);
    serviceLocator.RegisterInstance<IPhotoRepository>(photoRepository);
 
    _testNavigationService.ClearLastNavigationInfo();
    _testMessageService.ExpectedResults.Enqueue(MessageResult.Cancel);
 
    var vm = new PhotoViewModel();
    vm.UpdateNavigationContext(CreateNavigationContextWithId(2));
    vm.Delete.Execute();
 
    Assert.AreEqual(3, photoRepository.Photos.Count, "Photo count should not have changed");
    Assert.AreEqual(null, _testNavigationService.LastNavigationUri, "Should have canceled");
    Assert.AreEqual(null, _testNavigationService.LastNavigationParameters, "Should have canceled");
}

As you can see, there are several services that are being used in this unit test. One of them, the IPhoneRepository, is created inside the unit test. The other ones seem to be fields. The fields are initialized in the TestInitialize method which is shown below:

private Catel.MVVM.Services.Test.NavigationService _testNavigationService;
private Catel.MVVM.Services.Test.MessageService _testMessageService;
 
[TestInitialize]
public void Initialize()
{
    var serviceLocator = ServiceLocator.Instance;
 
    if (_testNavigationService == null)
    {
        _testNavigationService = new Catel.MVVM.Services.Test.NavigationService();
        serviceLocator.RegisterInstance<INavigationService>(_testNavigationService);
    }
 
    if (_testMessageService == null)
    {
        _testMessageService = new Catel.MVVM.Services.Test.MessageService();
        serviceLocator.RegisterInstance<IMessageService>(_testMessageService);
    }
}

Instead of the real MessageService, a test implementation of the MessageService is instantiated and registered in the IoC container.

Back to our unit test and focus on the part that is shown below:

_testNavigationService.ClearLastNavigationInfo();
_testMessageService.ExpectedResults.Enqueue(MessageResult.Cancel);
 
var vm = new PhotoViewModel();
vm.UpdateNavigationContext(CreateNavigationContextWithId(2));
vm.Delete.Execute();
 
Assert.AreEqual(3, photoRepository.Photos.Count, "Photo count should not have changed");
Assert.AreEqual(null, _testNavigationService.LastNavigationUri, "Should have canceled");
Assert.AreEqual(null, _testNavigationService.LastNavigationParameters, "Should have canceled");

The last navigation info of the INavigationService is cleared so we can make sure that no calls are made to the INavigationService during our unit test. We also enqueue the expected result for the IMessageService. The first call to the IMessageService will return the specified MessageResult.

In words, the test checks whether a cancel is correctly handled. The application should not navigate away and the number of images should stay the same.

PART II - Camera Service

5. Basics of the Camera Service

The CameraService provides a real implementation, but also a test implementation. The real implementation is automatically registered in the IoC container if a view model is instantiated the first time. If you don’t like to use the ViewModelBase that ships with Catel, you will have to register the camera service like this:

serviceLocator.RegisterInstance<ICameraService>(CameraService);

The CameraService follows the PhotoCamera API as much as possible. This way, there is no learning curve for using the

5.1. Starting and stopping the service

The PhotoCamera documentation continuously states that the camera object must be created and disposed properly. In the service, this is encapsulated by the StartService and StopService methods. To start the service, use the code below:

var cameraService = GetService<ICameraService>();
cameraService.CaptureThumbnailAvailable += OnCameraServiceCaptureThumbnailAvailable;
cameraService.CaptureImageAvailable += OnCameraServiceCaptureImageAvailable;
cameraService.Start();

To stop the service, use the code below: (note: the Close method is feature of Catel):

protected override void Close()
{
    var cameraService = GetService<ICameraService>();
    cameraService.Stop();
    cameraService.CaptureThumbnailAvailable -= OnCameraServiceCaptureThumbnailAvailable;
    cameraService.CaptureImageAvailable -= OnCameraServiceCaptureImageAvailable;
}

5.2. Capturing images

To capture images, several things must be done. The first action to accomplish is to subscribe to the ICameraService.CaptureImageAvailable event. The next step is to invoke the CaptureImage method like shown below:

CameraService.CaptureImage();

The last part is very important. You will need to read the image stream from the CaptureImageAvailable event:

BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(e.ImageStream);

5.3. Showing a video of camera in a view

To show a preview of the camera input on the phone, first subscribe to the ICameraService.CaptureThumbnailImageAvailable event. Next step is to create a property on the view model:

/// <summary>
/// Gets or sets the current photo.
/// </summary>
public BitmapImage CurrentPhoto
{
    get { return GetValue<BitmapImage>(CurrentPhotoProperty); }
    set { SetValue(CurrentPhotoProperty, value); }
}
 
/// <summary>
/// Register the CurrentPhoto property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentPhotoProperty = RegisterProperty("CurrentPhoto", typeof(BitmapImage));

This property definition is a Catel property, but if you prefer using a different MVVM framework or your own property definition style, you are free to do that as well.

In the view, use the Image control to show the current photo:

<Image Grid.Row="0"
Source="{Binding
CurrentPhoto}" />

Last but not least, we need to update the CurrentPhoto property when a new thumbnail is available.

private void OnCameraServiceCaptureThumbnailAvailable(object sender, ContentReadyEventArgs e)
{
    BitmapImage bitmap = new BitmapImage();
    bitmap.SetSource(e.ImageStream);
    CurrentPhoto = bitmap;
}

5.4. Instantiating the test implementation

The test implementation of the CameraService needs to be instantiated with an image. The service will use this image to create an animation. The animation that will be applied is the image scrolling to the right pixel by pixel.

To instantiate the test service, add an image to the Windows Phone 7 project and set its build action to Resource. Then instantiate the service like in the code below:

var testImage = new BitmapImage();
var streamResourceInfo = Application.GetResourceStream(new Uri("/MyAssembly;component/Resources/Images/MyImage.png", UriKind.RelativeOrAbsolute));
testImage.CreateOptions = BitmapCreateOptions.None;
testImage.SetSource(streamResourceInfo.Stream);
_testCameraService = new CameraService(testImage);
serviceLocator.RegisterInstance<ICameraService>(_testCameraService);

By default, the CameraService will generate a new thumbnail image every 50 milliseconds. It is possible to customize this with a constructor overload.

5.5. Customizing camera settings for testing

Sometimes it is required to test different resolutions. One way to do this is to buy all available Windows Phone 7 devices and test the software on all the cameras. An easier way is to use the ICameraService and customize the camera options to test how the application responds to the different settings.

The settings are stored in the CameraServiceTestData class. This class allows customizing all the properties normally found on the PhotoCamera class. For example, to only allow the primary camera (because front facing cameras are not supported by all devices), use the following code:

var cameraTestSettings = new CameraServiceTestData();
cameraTestSettings.SupportedCameraTypes = CameraType.Primary;
cameraService.UpdateTestData(cameraTestSettings);

It is also possible to change the thumbnail and final resolution of the images:

var cameraTestSettings = new CameraServiceTestData();
cameraTestSettings.PreviewResolution = new Size(400, 800);
cameraTestSettings.Resolution = new Size(1200, 2400);
cameraService.UpdateTestData(cameraTestSettings);

6. Using the CameraService in the emulator

First of all, there must be some way of determining whether the code is running in the emulator or on a real device. In the App.xaml.cs of the application, register the test version of the camera service in the IoC container:

var serviceLocator = ServiceLocator.Instance;

// Since we are running in an emulator, use the test version of the ICameraService
var testImage = new BitmapImage();
var streamResourceInfo = Application.GetResourceStream(new Uri("/MyAssembly;component/Resources/Images/MyImage.png", UriKind.RelativeOrAbsolute));
testImage.CreateOptions = BitmapCreateOptions.None;
testImage.SetSource(streamResourceInfo.Stream);
serviceLocator.RegisterInstance<ICameraService>(new MVVM.Services.Test.CameraService(testImage));

If the service is correctly implemented in the view model as explained in showing a video of camera in a view, you will see the thumbnails being updated on the main page:

image008.jpg

7. Using the CameraService in unit tests

So far, we have only covered basic unit testing for Windows Phone 7. Now let’s take a look on how to test whether the camera implementation works correctly in the application.

We start with writing the TestInitialize method which instantiates the test instance:

[TestClass]
public class MainPageViewModelTest
{
    private CameraService _testCameraService;

    [TestInitialize]
    public void Initialize()
    {
        var serviceLocator = ServiceLocator.Instance;
 
        if (_testCameraService == null)
        {
            var testImage = new BitmapImage();
            var streamResourceInfo = Application.GetResourceStream(new Uri("/MyAssembly;component/Resources/Images/MyImage.png", UriKind.RelativeOrAbsolute));
            testImage.CreateOptions = BitmapCreateOptions.None;
            testImage.SetSource(streamResourceInfo.Stream);
            _testCameraService = new CameraService(testImage);
            serviceLocator.RegisterInstance<ICameraService>(_testCameraService);
        }
    }
}

Now the view models that are being unit tested will use the test implementation.

8. Conclusion

This article has shown why we, the developers of Catel, have chosen to implement the PhotoCamera class as a service. We hope that you truly understand why we created this service and how powerful it is.

Some developers really don’t want to use Catel for some reasons (for example, because other people tell them to use MVVM light (or another MVVM framework)). In that case, it is still possible to use all the services provided by Catel. Just register all the services manually in your IoC container of choice.

We’d love to hear your feedback!

More information about Catel can be found at http://catel.codeplex.com

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 3:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140905.1 | Last Updated 2 Sep 2011
Article Copyright 2011 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid