To understand this article it’s rather essential that you’ve read the previous 2 articles, in case you didn’t read those articles yet I’d advise to go back and read it now, start with the article how to create a unit test project.
.ads in wordpress
Prerequisites for this tutorial
This tutorial is part of a tutorial series:- Set Up a Connect IQ development environment
- 1: Create a Connect IQ data field
- 2: Use dc.draw() calls for layout
- 3: Using string resources
- 4: How to create a unit test project
- 5: how to run unit tests
First Unit Test: Exposing private variables
Purpose of the test
In our first actual connect IQ unit test we want to verify that the intial value of the Total Steps is set to 0.Problem
A problem is that we need access to the total steps value to verify it, but that field is private (hidden in MonkeyC terms)…Solution
The solution is quite easy as our mock StepsCarouselMock inherits from StepsCarouselView we can expose the private variable value in a public getter getValue(). So we add the following function to our mock:
1 2 3 |
function getValue() { return value; } |
- In the file StepsCarouselTests.mc add the code for the first test:
1 2 3 4 5 6 7 |
(:test) function initialStepsTotalIsZero(logger) { var view = new StepsCarouselMock(); var value = view.getValue(); Toybox.Test.assertEqualMessage(value,0.0,"Initial Steps Total is expected to be 0.0"); return true; } |
- var view = new StepsCarouselMock(); -> create a new instance of our mocked StepsCarouselView
- var value = view.getValue(); -> get the value of the field “value”, in StepsCarouselView this variable is private, but we’ve exposed it’s value in the getValue() function.
- Toybox.Test.assertEqualMessage(… -> compare the actual value with the expected value and define the message to show when the test fails
- return true; -> if a test function returns false then the test fails, you can misuse this functionality to do your assertions in this return statement, but personally I prefer to asserts with actual assert functions because I think it’s cleaner and more obvious when a test fails.
Run the tests
Create a run test profile for your project and execute the test, in the console window you should see the following pass by (for each watch target):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
------------------------------------------------------------------------------ Executing test helloWorld... DEBUG (21:13): Hello World PASS ------------------------------------------------------------------------------ Executing test initialStepsTotalIsZero... PASS ============================================================================== RESULTS Test: Status: helloWorld PASS initialStepsTotalIsZero PASS Ran 2 tests PASSED Connection Finished Closing shell and port Press any key to continue . . . |
Second unit test: Mock external dependencies
Purpose of the test
we want to verify that when the timer is not running that recorded steps do not increase.Problem
The problem here that we want to test the compute function, but inside that function there is a dependency on the behaviour of an external component (Toybox.ActivityMonitor)Solution
The solution for this issue is to replace this external dependency by something that we can access. We will in other words have to create a mock for the Toybox.ActivityMonitor and instead of calling the real one call our mock instead. In our business project we still want to call the real one though…In the StepsCarouselView.mc file we introduce a new function getActivityMonitorInfo, this function will return the real value of the component:
1 2 3 |
function getActivityMonitorInfo() { return Toybox.ActivityMonitor.getInfo(); } |
When unit testing we don’t want to depend on this external dependency
So let’s create a new mock which has the same fields as the real function.Edit the file StepsCarouselMocks.mc and add a mock for the ActivityMonitorInfo… something like this should do it:
1 2 3 4 |
class ActivityMonitorInfoMock { var steps = 0; var stepGoal = 5000; } |
1 2 3 4 5 6 7 8 9 |
hidden var activityMonitorInfo = new ActivityMonitorInfoMock(); function getActivityMonitorInfo() { return activityMonitorInfo; } function setActivityMonitorInfo (info) { activityMonitorInfo = info; } |
1 2 3 |
function getStepsRecorded() { return stepsRecorded; } |
Let’s create the unit test
Ok we’re finally ready to create the test now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
(:test) function whenTimerDoesNotRunRecordedStepsDontIncrease(logger) { var view = new StepsCarouselMock(); // create the ActivityMonitorInfoMock var activityMonitorInfo = new ActivityMonitorInfoMock(); view.setActivityMonitorInfo(activityMonitorInfo); // set steps to 15 activityMonitorInfo.steps = 15; // inject the ActivityMonitorInfoMock into the StepsCarouselMock view.setActivityMonitorInfo(activityMonitorInfo); // execute business logic view.compute(null); Toybox.Test.assertEqualMessage(activityMonitorInfo.steps,15,"Steps are 15"); Toybox.Test.assertEqualMessage(view.getStepsRecorded(),0,"Recorded Steps are 0"); return true; } |
Want some practice?
If you want some practice, try to create the following unit tests:- when the timer runs, then recorded steps should increase
- when the timer is stopped, then the total steps (of the day) are shown
- when the timer is stopped, then the label does not change after 5 seconds
- every 5 seconds the value of the label should change (create the test + fix the bug)
Source Code
You can download the full source code from githubFeedback
Did you like this article?Questions?- Post in the comments section below!