Testing

TIBET Testing

wins

  • Single unified test harness for unit and functional testing client code.
  • Tests are associated with target objects for focused testing and coverage.
  • Tests can be run for a single method, single instance, Type, or prototype.
  • Tests can be run from the command line, within the Sherpa, or via Karma.
  • CI/CD integration is easy to add via the supported karma-tibet add-on.

concepts

TIBET is different from other JavaScript platforms. TIBET is, as we say, unabashedly Object-Oriented. As a result we felt we'd be remiss if all we did was copy test patterns designed with 'page' or 'function' as their quantum of development. In TIBET you test objects.

Testing Objects

A fundamental principle of object-oriented languages is that instances of a type should behave consistently with instances of any supertypes. This implies tests you write for a supertype should, if properly written, pass for instances of any subtype. If they don't then you've created a subtype that violates the guarantee (or written tests with inappropriate dependencies).

In TIBET your tests are always associated with an object, typically a type, a type's instance prototype, or an individual method. When you invoke a test run TIBET reflects on the particular target, asking it for its tests, and running the tests specific to that target.

Using reflection to drive testing has several powerful implications:

  • You can ask any type or method to run its tests. Every object has a test suite;
  • A type can ask its supertypes for their tests and run them as well;
  • A method can ask supertype implementations for their tests and run them;
  • TIBET's tools can tell you which types and methods have tests and which don't.

By associating your tests with an object TIBET enables a completely different approach to OO testing, one that supports inheritance-aware testing as well as built-in coverage reporting at the type and method level of granularity.

If you want to test a "page" you can always create an Object to represent that page and associate any tests for that page with the target object. There's no limit on how you decide to organize your tests, other than they have to be associated with an object, any object.

Command Line Testing

Interactive testing in TIBET is accomplished via the TIBET CLI which is available both in the client and on the server.

When you're running on the server you can invoke the tibet test command:

tibet test [target|suite|"all"] [--target <target>] [--suite <suite>] [--cases <casename>] [--ignore-only] [--ignore-skip] [--no-tap] [--remote-debug-port <portnumber>]

TIBET supports the npm standard for invoking tests as well:

npm test

TIBET's ability to run the test harness from the command line gives you an easy way to integrate your tests with a continuous integration (CI) system.

TIBET Shell / TDC Testing

In the client CLI (available in both the Console and IDE) you'd enter:

:test [target|suite|"all"] [--target='<target>'] [--suite='<suite>'] [--cases='<casename>'] [--ignore-only] [--ignore-skip] [--no-tap]

OR, if you have an object to message:

object.runTestSuites();

Sherpa™ Test Execution

In the TIBET Sherpa you can run your project's tests simply by clicking the 'test project' icon on the toolbar:

You can also use the "Run Tests" option from the Halo context menu to run tests for the object/tag you've currently got under the Halo.

Test Definition Syntax

Our goal for TIBET's testing API was to preserve as much of the flavor of the popular describe and it syntax for test definition as possible while supporting our goals for OO testing. What we ended up with is a message-based variation on describe/it.

In TIBET you define tests using messages targeting the object you're testing. The following examples assume a type TP.example.Foo exists so we can associate tests with that type and its various aspects.

//  Add a test to the Foo Type
TP.example.Foo.Type.describe('a type test suite', function() {
    this.it('has a construct method', function(test, options) {
    ...
    });
});

//  Add a test Foo Type instances
TP.example.Foo.Inst.describe('an instance test suite', function() {
    this.it('has a double method', function(test, options) {
    ...
    });
});

//  Add a test to the 'example' namespace.
TP.example.describe('a namespace test suite', function() {
    this.it('has a Foo type', function(test, options) {
    ...
    });
});

//  Add a test to the Foo Type's 'construct' method
TP.example.Foo.Type.describe('construct', function() {
    this.it('requires a parameter', function(test, options) {
    ...
    });
});

//  Add a test for the Foo instance 'double' method
TP.example.Foo.Inst.describe('double', function() {
    this.it('should double its input', function(test, options) {
    ...
    });
});

Notice that we're invoking describe as a method, not a global function. Also, we're invoking this.it rather than calling a global it() function. TIBET is heavily oriented toward messaging over global functions and our testing infrastructure is no exception.

this And test

When your tests are run they're invoked such that the this reference within your describe function points to a TP.test.Suite instance and the this reference within your it functions (the actual tests themselves) point to the current TP.test.Case instance.

The it method is, in fact, a method on TP.test.Suite instances that describes a new TP.test.Case for that instance of test suite.

In a similar fashion, the TP.test.Case object has a number of methods found on its assert property which provide common assertions used in your tests.

In short, methods on TP.test.Suite and TP.test.Case allow you to fail the suite/case, time it out, or control it in other ways. There are no global functions in the test harness, everything is done via object methods.

Verifying Expectations

TIBET currently supports two ways of verifying expectations in your tests, a classic TDD 'assert' API , and a BDD 'expect' API.

assert

In earlier examples we showed that TIBET replaces global functions with methods. This is also true for the 'assert' interface. When defining your assertions you use this.assert followed by the specific assertion such as isMethod below:

this.assert.isMethod(testObj.getMethod('double'));

To easily test for negative conditions, TIBET also provides 'refute':

this.refute.isMethod(testObj.getMethod('single'));

There are a wide variety of specific assert-prefixed tests you can leverage in your tests. For the full list see the TP.test.TestMethodCollection object API. You can view this list in the TIBET Developer Console using the following TIBET Shell command:

TP.test.TestMethodCollection.Inst.getMethods() .|* 'name' .?* /^is/

There are over 100 specific assertions supported by the TIBET assert API.

expect

In TIBET the 'expect' API is similar to 'assert' in that you invoke a method rather than calling a global function:

this.expect(testObj.getMethod('double')).to.be.a(Function);
this.expect(testObj.getMethod('single')).not.to.be.a(Function);

Note that TIBET's expect API is based on that of the ChaiJS library. There are a few differences, however:

  • TIBET does not support the .and() chain call (or any 'mid-chain' 'calls' for that matter - all parts of the chain must be 'properties' until the last segment):

//  ChaiJS:
expect([]).to.be.an('array').and.not.be.arguments;
//  TIBET:
var testObj = TP.ac();
this.expect(testObj).to.be.an('Array') && this.expect(testObj).to.not.be.args();

  • TIBET requires that the end of the chain be a real call:

//  ChaiJS:
expect(null).to.be.null;
//  TIBET:
this.expect(null).to.be.null();

  • TIBET omits or renames a number of ChaiJS methods:

'and'           omitted (see above)
'exist'         added 'valid' alias
'arguments'     renamed to 'args'
'eql'           renamed to 'identical'
'instanceOf'    omitted (use 'a' or 'an' with a TIBET type or native constructor instead)
'length'        omitted and added 'size' alias

The full set of TIBET expectation 'words' is as follows. Note that the ones marked 'chain-only' are those which are only used for chaining. They don't have a real implementation:

'a'            //  real implementation
'above'        //  real implementation
'an'           //  real implementation
'args'         //  real implementation
'at'           //  chain-only
'be'           //  chain-only
'been'         //  chain-only
'below'        //  real implementation
'closeTo'      //  real implementation
'contain'      //  real implementation
'deep'         //  real implementation
'empty'        //  real implementation
'equal'        //  real implementation
'equals'       //  real implementation
'eq'           //  real implementation
'exist'        //  real implementation
'false'        //  real implementation
'greaterThan'  //  real implementation
'gt'           //  real implementation
'identical'    //  real implementation
'include'      //  real implementation
'itself'       //  real implementation
'is'           //  chain-only
'has'          //  chain-only
'have'         //  chain-only
'key'          //  real implementation
'keys'         //  real implementation
'least'        //  real implementation
'lengthOf'     //  real implementation
'lessThan'     //  real implementation
'lt'           //  real implementation
'match'        //  real implementation
'members'      //  real implementation
'most'         //  real implementation
'not'          //  real implementation
'null'         //  real implementation
'ok'           //  real implementation
'of'           //  chain-only
'ownProperty'  //  real implementation
'property'     //  real implementation
'respondTo'    //  real implementation
'same'         //  chain-only
'satisfy'      //  real implementation
'size'         //  real implementation
'string'       //  real implementation
'that'         //  chain-only
'throw'        //  real implementation
'throws'       //  real implementation
'to'           //  chain-only
'true'         //  real implementation
'undefined'    //  real implementation
'valid'        //  real implementation
'with'         //  chain-only
'within'       //  real implementation

Pass / Fail

Normally the assert/expect API is enough to manage pass/fail for your tests.

When your test logic is too complex for a simple assert or expect call you can fail tests manually by calling this.fail():

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    this.it('should do X', function(test, options) {
        ...
        if (some_nasty_condition) {
            this.fail();
        }
    });
});

Detached Tests

If you're used to using xUnit-style test harnesses you may find the whole idea of attaching tests directly to objects a bit foreign. You might find yourself simply wanting to create tests which don't appear to have a natural owner.

Strictly speaking you can never have a truly detached test in TIBET (as everything is an Object). But you can obviously create a random object whose only purpose is to give you a place to create 'detached tests'.

For a variety of reasons we recommend you avoid this practice; there are usually better ways to organize tests. In fact, the sense that a test has no good owner is often a sign that you're missing a controller. Still, if you must, just use a regular object.

TP.test.MyTester = TP.lang.Object.construct();

TP.test.MyTester.describe('A random test suite', function(testObj, options) {
    this.it('should do random testing', function(test, options) {
        ...
    });
})

and to execute:

TP.test.MyTester.runTestSuites();

Since you can create any object configuration you like and still rely on inheritance and reflection to help you find tests, you can choose to organize your test functions on an independent object hierarchy if you like.

TIBET is flexible enough to let you configure your tests any way you like. Just be aware that when you use a detached test approach you're breaking TIBET's ability to give you full coverage analysis at the type and method level.

Maintaining Test Context

Tests should be run in a consistent context, one free of any leftover data or other things which might influence the test. To ensure tests have a consistent environment and a consistent set of baseline data you can make use of setup and teardown functions.

Setup

Depending on your requirements you may need to perform pre-test configuration at either the test suite or test case level. This can be done using either the before or beforeEach call on TP.test.Suite.

test.Suite before()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {

    // Perform one-time setup which runs _BEFORE THE FIRST_ test case.
    this.before(function(suite, options) {
        ...
    });
    ...
});

test.Suite beforeEach()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {

    // Perform setup which runs _BEFORE EACH_ test case.
    this.beforeEach(function(test, options) {
        ...
    });
    ...
});

Teardown

If your tests rely on the creation of objects, or establisment of connections, or any other infrastructure which should be undone after completion you can use either after or afterEach methods to accomplish that.

test.Suite after()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {

    // Perform one-time teardown which runs _AFTER THE LAST_ test case.
    this.after(function(suite, options) {
        ...
    });
    ...
});

test.Suite afterEach()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {

    // Perform teardown which runs _AFTER EACH_ test case.
    this.afterEach(function(test, options) {
        ...
    });
    ...
});

Setting Time Limits

Depending on the nature of your tests you may need to define a time limit for either your test suite or for specific test cases. As with much of the TIBET test harness functionality you can accomplish this by messaging the test.Suite or test.Case directly.

The timeout message sent to either your test suite or test case takes a number of milliseconds. If the test takes any longer than that it will automatically be fail()'d with a TestTimeout exception.

test.Suite timeout()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    // If the suite takes longer than 2 seconds time it out.
    this.timeout(2000);
    ...
});

test.Case timeout()

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    ...
    // If this test case takes more than a second to run time it out.
    this.it('should do X', function(test, options) {
        this.timeout(1000);
        ...
    });
    ...
});

Deferring Tests

Note: Deferring tests is not currently supported in this release

When you're following good Test-Driven Development processes it's not unusual to realize the need for tests which you know you can't possibly pass at the moment, a test you should write now, in the moment, but which you don't want to run yet.

You can defer a test or test suite by calling defer() on the value returned by it or describe depending on your goal. The return value from describe is a TP.test.Suite and the return value from test.Suite's it method is a TP.test.Case.

As you can see in the examples provided below the defer call takes a reason which is output during test execution to help you remember why a test has been deferred and help trigger you to remove the deferral when it not longer applies.

test.Suite defer()

In the example below we're deferring this entire test suite:

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    ...
}).defer('This needs to wait until we do Z');

test.Case defer()

In the example below we've deferred the first test case (should do X):

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    ...
    this.it('should do X', function(test, options) {
        ...
    }).defer('This needs to wait until we do Z');
    ...
});

Exclusive Tests

Test execution happens at the test suite level, meaning test suites typically run as an all-or-nothing affair. When you're in the heat of fixing a specific test case failure however that can mean extra development delays.

To keep your testing cycles short when you're focusing on a single test case you can use the only method.

test.Suite only()

When you've defined multiple test suites for a target but only want to run a particular suite you can mark it with only as shown below:

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    ...
}).only();

test.Case only()

When you're drilling down to a specific test case you can mark it as the only one to run by messaging the test.Case returned from the it call:

TP.example.Foo.Type.describe('Test Foo Type', function(testObj, options) {
    ...
    this.it('should do X', function(test, options) {
        ...
    }).only();
    ...
});

Spies, Stubs, and Mocks

When you're building tests you often have to verify:

  • certain methods are accessed (or not) as expected,
  • that specific actions be taken (or not taken), and
  • that specific expections are consistently met.

These requirements sum up the basic roles of spies, stubs, and mocks respectively.

TP.test and Sinon

TIBET maps the excellent Sinon library API onto the TP.test object for use in adding spies, stubs and mocks to your tests. The TP.test object provides access to the entire API of the sinon object.

Here's a partial list of the API you can access via TP.test:

var spy = TP.test.spy();
var stub = TP.test.stub();
var mock = TP.test.mock();

var clock = TP.test.useFakeTimers();

var xhr = TP.test.useFakeXMLHttpRequest();

var server = TP.test.fakeServer.create();

var sandbox = TP.test.sandbox();

Sinon/TIBET "Sugars"

While all of Sinon is available to you via the TP.test namespace there are aspects of TIBET's OO system which make using unsugared Sinon API cumbersome.

In TIBET if you want to stub a method you have to get access to the function, create the stub, then position the stub in place of the original method on the proper prototype or target. Sinon's API can't do that for you automatically, but TIBET does provided you rely on TIBET's APIs.

Stubs

Stubs are spies with special behavior: they never invoke their wrapped function, but may invoke an alternate implementation supplied to them.

Like spies, you can have anonymous stubs or stubs which wrap a function. Unlike spies, with a stub you can optionally provide an alternative implementation for the function you're wrapping. By default a stub is effectively an anonymous spy.

See the Cookbook section for numerous examples of using stubs in TIBET.

Mocks

To quote the Sinon documentation:

Mocks (and mock expectations) are fake methods (like spies) with pre-programmed
behavior (like stubs) as well as pre-programmed expectations.

Using mocks in TIBET is identical to using them in Sinon, with the one exception being that you access the initial Mock constructor via TP.test:

var mock = TP.test.mock(anObj);

NOTE: Sinon.js mocks support a BDD 'expect'-style API that differs from TIBET's. TIBET's interface is modeled on Chai.js.

Once you have the mock reference the rest is standard Sinon API:

// Setting an expection for a method on a mock:
mock.expects('double').atLeast(1).atMost(1);

// Restoring original methods on a mocked object:
mock.restore();

cookbook

Verifying APIs (Interfaces)

As we saw in our earlier walkthrough of TDD in TIBET your first steps are often creating new types to support an API. The next thing you want to do is set up breaking tests which verify your API, then add method stubs to pass the tests, add method unit tests, and finally create working method implementations. The first step in that process is testing API existence.

Testing Type Interfaces

For this test we attach our test suite to the Foo type we're testing. The individual test cases simply ensure that each of the methods in our API exist:

TP.example.Foo.Type.describe("Test Foo's Type API", function(testObj, options) {

    this.it('should implement construct', function(test, options) {
        this.expect(testObj.construct).to.be.a(Function);
    });

    this.it('should implement sample', function(test, options) {
        this.expect(testObj.sample).to.be.a(Function);
    });
});

In TIBET it's not uncommon to find an Array containing a list of method names registered as an API. Using an approach similar to the one above you can loop over those arrays and confirm your objects at least have an implementation.

Testing Instance Interfaces

You can test the API of an instance of the Foo type using identical test syntax but targeted at Foo's inst reference:

TP.example.Foo.Inst.describe("Test Foo's Instance API", function(testObj, options) {

    this.it('should implement double', function(test, options) {
        this.expect(testObj.double).to.be.a(Function);
    });

    this.it('should implement triple', function(test, options) {
        this.expect(testObj.triple).to.be.a(Function);
    });
});

Testing Object/Namespace Interfaces

While it's not common to want to test the API of an anonymous object, there's a pretty common kind of generic object you'll often encounter in JavaScript, a so-called "namespace".

JavaScript namespace objects shouldn't be confused with XML namespaces. A JavaScript namespace is basically a generic object instance which acts as a collection of related methods. Namespace objects are the perfect target for API tests.

In the example below we test our TP.test namespace for three of the Sinon API methods we expect it to publish:

TP.test.describe('Test the Test API', function(testObj, options) {

    this.it('should implement spy', function(test, options) {
        this.expect(testObj.spy).to.be.a(Function);
    });

    this.it('should implement stub', function(test, options) {
        this.expect(testObj.stub).to.be.a(Function);
    });

    this.it('should implement mock', function(test, options) {
        this.expect(testObj.mock).to.be.a(Function);
    });
});

Verifying Attributes

Verifying attributes means checking to ensure that the public attributes of an object are accessible and defined.

In TIBET the addAttribute method variations all initialize new attributes to null. This makes it easy to tell the difference between something that should exist but which has no value, and something that isn't a true attribute.

One thing to note in attribute tests. We do not use direct slot access. In other words we do not use testObj.config below. When testing attributes of objects in TIBET you should always test through get(), just as you would access them via code.

Verifying Type Properties

TP.example.Foo.Type.describe('Test Foo Type Properties', function(testObj, options) {
    this.it('should publish a test slot', function(test, options) {
        this.expect(testObj.get('test')).to.be.valid();
    });
});

Verifying Instance Properties

TP.example.Foo.Inst.describe('Test Foo Instance Properties', function(testObj, options) {
    this.it('should publish a sample slot', function(test, options) {
        this.expect(testObj.get('sample')).to.be.valid();
    });
});

Verifying Object/Namespace Properties

TP.test.describe('Test the Test properties', function(testObj, options) {
    this.it('should publish a config slot', function(test, options) {
        this.expect(testObj.get('config')).to.be.valid();
    });
});

Verifying Constants

Testing for constants is similar to testing for attributes but in the case of a constant you don't use get(), you use direct slot access since that's how constants are used within source code.

Here's a test to ensure our Foo type exports an important constant:

TP.example.Foo.Type.describe('Test Foo Type Constants', function(testObj, options) {
    this.it('should export a TIBET constant', function(test, options)
        this.expect(testObj.TIBET).to.be.valid();
    });
});

Verifying Instance Constants

For targeting an instance we just add the inst reference to our type:

TP.example.Foo.Inst.describe('Test Foo Instance Constants', function(testObj, options) {
    this.it('should export a TIBET constant', function(test, options)
        this.expect(testObj.TIBET).to.be.valid();
    });
});

Verifying Object/Namespace Constants

When you test a namespace you just target the namespace object itself:

TP.test.describe('Test "test" namespace Constants', function(testObj, options) {
    this.it('should export a TIBET constant', function(test, options)
        this.expect(testObj.TIBET).to.be.valid();
    });
});

Testing Methods

The TIBET test APIs will automatically try to associate tests you describe with their matching methods provided you describe the test using the method name. In this way you don't need to directly target a method object to add tests for it, simply target the Type, Inst, or local object and name one or more tests using the name of the method you want to ultimately test.

For example, here is a sample taken from the TIBET URI tests to test construct:

TP.uri.TIBETURL.Inst.describe('construct',
function() {

    this.it('TIBET URN uniques instances regardless of format',
            function(test, options) {
        var inst;

        inst = TP.uc('urn::imatesturi');
        inst.setResource('fluffy');

        test.assert.isIdenticalTo(inst, TP.uc('urn::imatesturi'));
        test.assert.isIdenticalTo(
            inst.getResource().get('result'),
            TP.uc('urn::imatesturi').getResource().get('result'));

        test.assert.isIdenticalTo(
            inst.getResource().get('result'),
            TP.uc('urn:tibet:imatesturi').getResource().get('result'));
    });
});

Using Spies

Creating An Anonymous Spy

Anonymous spies don't wrap functions, so you create them via the TP.test namespace.

var spy = TP.test.spy();

Spying On A Function

var spy = TP.test.spy(function() {...});

// OR, if you have a function (func in this example):

var spy = TP.test.spy(func);

In TIBET, every function can return also itself in spy form in response to asSpy:

var spy = func.asSpy();

Spying On A Type Method

There's shorthand for wrapping type methods which is similar to defining the method to begin with, spyOn:

var spy = TP.example.Foo.Type.spyOn('getBar');

You can alternatively use asSpy on a reference to the type method itself:

var spy = TP.example.Foo.Type.getBar.asSpy();

Spying On An Instance Method

Instance method spies can also be installed via spyOn:

var spy = TP.example.Foo.Inst.spyOn('double');

You can alternatively use asSpy on a reference to the instance method itself:

var spy = TP.example.Foo.Inst.double.asSpy();

Spying On A Local Method

If you only want the spy to be installed on a single instance, not all instances, use spyOn just on that object:

var inst = TP.example.Foo.construct();
var spy = inst.spyOn('double');

You can alternatively use asSpy on a reference to the local method itself:

var inst = TP.example.Foo.construct();
var spy = inst.double.asSpy();

When a spy is obtained in this way from TIBET, the full Sinon API for 'spies installed on existing methods' is available to you. See the Sinon documentation for more information.

Using Stubs

Creating An Anonymous Stub

Anonymous stubs don't wrap functions, so you create them via the TP.test namespace.

var stub = TP.test.stub();

Stubbing A Function

You can't really stub a Function outside of a method context with Sinon, so TIBET doesn't support stubbing a non-bound Function either.

Stubbing A Type Method

There's shorthand for wrapping type methods which is similar to defining the method to begin with, stubOn:

var stub = TP.example.Foo.Type.stubOn('getBar');

You can alternatively use asStub on a reference to the type method itself:

var stub = TP.example.Foo.Type.getBar.asStub();

Stubbing An Instance Method

Instance method spies can be installed via stubOn. This avoids the need to get a handle to the method itself.

var stub = TP.example.Foo.Inst.stubOn('double');

You can alternatively use asStub on a reference to the instance method itself:

var stub = TP.example.Foo.Inst.double.asStub();

Stubbing A Local Method

If you only want the stub to be installed on a single instance, not all instances, use stubOn just on that object:

var inst = TP.example.Foo.construct();
var stub = inst.stubOn('double');

You can alternatively use asStub on a reference to the local method itself:

var inst = TP.example.Foo.construct();
var stub = inst.double.asStub();

Providing Alternative Behavior

What makes stubs interesting relative to spies is they provide alternative behavior. If you stub an existing method, calls to that method no longer perform any activity. To provide alternative functionality add a function to your stub call variant:

var logger = function() { console.log('called a stub!') };

var stub = TP.test.stub(logger);
var stub = TP.test.stub(function() {...}, logger);
var stub = TP.test.stub(func, logger);

//  Stub a type-level method
var stub = TP.example.Foo.Type.stubOn('getFoo', logger);

//  Stub a type *local* method
var stub = TP.example.Foo.stubOn('getBaz', logger);

//  Stub a instance-level method
var stub = TP.example.Foo.Inst.stubOn('getBar', logger);

//  Stub an instance *local* method
var inst = TP.example.Foo.construct();
inst.stubOn('getBar', logger);

In each of the variants above we've added a reference to a logger function which will serve as the method body for the stub.

Restoring An Original Function/Method

The general answer to restoring a spied-upon or stubbed function is to message the spy/stub via restore:

spy.restore();

// OR

stub.restore();

There are also restore variants for type, instance, and local methods:

//  Type
TP.example.Foo.Type.restoreMethod('getFoo');

//  Type local
TP.example.Foo.restoreMethod('getBaz');

//  Instance
TP.example.Foo.Inst.restoreMethod('getBar');

//  Instance local
inst.restoreMethod('getBar');

code

For example tests of virtually every kind check out the ~lib/test/src directory. There a thousands of TIBET tests at the object and UI level including mocking, GUI driver samples, and pretty much anything else you might want to leverage for insight.

Code for the overall test harness and its related types is found in the ~lib/src/tibet/testing directory in your TIBET release. The TP.test.Root, TP.test.Suite, and TP.test.Case files are the key ones to check for API related to test suites and test cases.

Code for 'assert' is found in ~lib/src/tibet/testing/TP.test.TestMethodCollection.js Code for 'expect' is found in ~lib/src/tibet/testing/TP.test.Expect.js