Skip to content

Commit

Permalink
Merge develop for initial release
Browse files Browse the repository at this point in the history
* develop:
  Initial Import of Framework
  • Loading branch information
gcaufield committed Sep 26, 2020
2 parents fa36170 + 6438d50 commit a7fac2c
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 6 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# MonkeyMock
# MonkeyTest

A ConnectIQ mocking framework
A ConnectIQ testing framework.

## Description

Class driven testing framework with support for Test suites and Mocks. Provides
clean output of test results, and simplifies the process of writing good unit
tests for ConnectIQ applications and libraries
10 changes: 7 additions & 3 deletions manifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. --><iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
<iq:barrel id="7c46f82e-9b95-4cb1-9c8e-126ebe2f5f10" module="MonkeyMock" version="0.0.0">
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. -->
<iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
<iq:barrel id="7c46f82e9b954cb19c8e126ebe2f5f10" module="MonkeyTest" version="0.0.1">
<iq:products/>
<iq:permissions/>
<iq:languages/>
<iq:barrels/>
<iq:annotations/>
<iq:annotations>
<iq:annotation>Mocks</iq:annotation>
<iq:annotation>Tests</iq:annotation>
</iq:annotations>
</iq:barrel>
</iq:manifest>
2 changes: 1 addition & 1 deletion mb_runner.cfg
Original file line number Diff line number Diff line change
@@ -1 +1 @@
APP_NAME="MonkeyInject"
APP_NAME="MonkeyTest"
1 change: 1 addition & 0 deletions monkey.jungle
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
project.manifest = manifest.xml
base.sourcePath = source

17 changes: 17 additions & 0 deletions source/Mocks/Exceptions/ExpectationException.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! ExpectationException.mc
//!
//! Copyright Greg Caufield 2020

using MonkeyTest.Tests;

module MonkeyTest {
(:Mocks)
module Mocks{
class ExpectationException extends Tests.TestException {
function initialize() {
TestException.initialize("Expectation not met");
}
}
}
}

18 changes: 18 additions & 0 deletions source/Mocks/Exceptions/UnexpectedInvocationException.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! UnexpectedInvocationException.mc
//!
//! Copyright Greg Caufield 2020

using MonkeyTest.Tests;

module MonkeyTest {
(:Mocks)
module Mocks {
//! Exception for a function invocation that was not declared
class UnexpectedInvocationException extends Tests.TestException {
function initialize(functionName) {
TestException.initialize("");
self.mMessage = "Unexpected Invocation of function " + functionName;
}
}
}
}
87 changes: 87 additions & 0 deletions source/Mocks/Expectation.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Expectation.mc
//!
//! Copyright Greg Caufield 2020

module MonkeyTest {

(:Mocks)
module Mocks {
//! Represents an expectation that has been set on a mock
class Expectation {
private var _expectedArgs = null;
private var _timesExpected = 1;
private var _timesCalled = 0;

function initialize() {
}

function withArgs(args) {
_expectedArgs = [];
_expectedArgs.addAll(args);
return self;
}

function isSaturated() {
if(_timesExpected == null) {
// If times expected is null then we don't care how many times this is
// called.
return false;
}

if((_timesCalled < _timesExpected)) {
return false;
}

return true;
}

function times(num) {
_timesExpected = num;
}

function isMatch(args) {
if(_expectedArgs == null) {
// No args specified, all calls match.
return true;
}

if(args.size() != _expectedArgs.size()) {
return false;
}

for(var i = 0; i < args.size(); i++) {
var expectedArg = _expectedArgs[i];

if(expectedArg == :any) {
continue;
}
else if((expectedArg instanceof Matcher) && !expectedArg.isMatch(args[i])) {
// Matcher check failed
return false;
}
else if( expectedArg != args[i]) {
return false;
}
}

return false;
}

function execute(args) {
if(isSaturated()) {
// TODO (caufield) create an exception for this
// Error Expectation Over Saturated
throw new ExpectationException();
}

_timesCalled++;
}

function verify() {
if((_timesExpected != null) && (_timesExpected != _timesCalled)) {
throw new ExpectationException();
}
}
}
}
}
14 changes: 14 additions & 0 deletions source/Mocks/Matcher.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Matcher.mc
//!
//! Copyright Greg Caufield 2020

module MonkeyTest {
(:Mocks)
module Mocks {
class Matcher {
function isMatch(other) {
return false;
}
}
}
}
103 changes: 103 additions & 0 deletions source/Mocks/Mock.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! Mock.mc
//!
//! Copyright Greg Caufield 2020
using Toybox.System;

module MonkeyTest {

(:Mocks)
module Mocks {
enum {
MOCK_TYPE_NICE,
MOCK_TYPE_NAGGY,
MOCK_TYPE_STRICT
}

//! Base class for Mock Classes
class Mock {
private var _expectations = {};
private var _type;

public function initialize(mockType) {
_type = mockType;
}

public function expect(key) {
var expectation = new Expectation();

if(!_expectations.hasKey(key)) {
_expectations[key] = [];
}

_expectations[key].add(expectation);
return expectation;
}

public function verifyAndClear() {
var keys = _expectations.keys();

for(var i = 0; i < keys.size(); i++) {
var expectations = _expectations[keys[i]];
for(var j = 0; j < expectations.size(); j++) {
expectations[j].verify();
}
}
}

protected function getFunctionName(key) {
return "Unknown";
}

//! Execute any expectations for the mock
protected function execute(key, arguments) {
var list = _expectations[key];

if(list == null) {
switch(_type) {
case MOCK_TYPE_NAGGY:
System.println(
"Unexpected call to Mock function " + getFunctionName(key));
return;
case MOCK_TYPE_NICE:
return;
default:
case MOCK_TYPE_STRICT:
throw new UnexpectedInvocationException(getFunctionName(key));
}
}

var candidate = null;
var match = false;

for(var i = 0; i < list.size(); i++) {
var expectation = list[i];
if(expectation.isMatch(arguments)) {
match = true;
if(expectation.isSaturated()) {
candidate = expectation;
continue;
}

// Unsaturated expectation that matches the arguments,
// call it and clear any potential saturated candidates.
expectation.execute(arguments);
candidate = null;
break;
}
}

if(!match) {
// TODO (caufield) Create an Exception for this
// No Valid Expectation for Arguments
throw new UnexpectedInvocationException(getFunctionName(key));
}

// A saturated candidate was found, we will call it so that an error will
// occur
if(candidate != null) {
candidate.execute(arguments);
}
}
}
}
}
17 changes: 17 additions & 0 deletions source/Tests/Exceptions/TestException.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! TestException.mc
//!
//! Copyright Greg Caufield 2020
using Toybox.Lang;

module MonkeyTest {
(:Tests)
module Tests {
//! Base Exception for Failures from the test framework
class TestException extends Lang.Exception {
function initialize(msg) {
Exception.initialize();
self.mMessage = msg;
}
}
}
}
35 changes: 35 additions & 0 deletions source/Tests/Test.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Test.mc
//!
//! Copyright Greg Caufield 2020
module MonkeyTest {

(:Tests)
module Tests {
//! Base class of the Test Suite
class Test {
//! Child classes are expected to override this and return the suite name
public function name() {
return "Unknown";
}

//! Child classes are expected to override this and return a dictionary
//! mapping test symbols to thier names
public function testList() {
return {};
}

function setUp() {
}

function tearDown() {
}

function expectEq(expected, actual) {
if( actual != expected ) {
throw new TestException("Expected equality. " + actual + " != " + expected);
}
}
}
}
}

Loading

0 comments on commit a7fac2c

Please sign in to comment.