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 Lama, or via Karma.
- CI/CD integration is easy to add via the supported
karma-tibet
add-on.
Contents
Concepts
- Testing Objects
- Command Line Testing
- Lama™ Test Execution
- Test Definition Syntax
- this And test
- Verifying Expectations
- Pass / Fail
- Detached Tests
- Maintaining Test Context
- Setting Time Limits
- Deferring Tests
- Exclusive Tests
- Spies, Stubs, and Mocks
Cookbook
- Verifying APIs (Interfaces)
- Verifying Attributes
- Verifying Constants
- Testing Methods
- Using Spies
- Using Stubs
Code
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.
Lama™ Test Execution
In the TIBET Lama 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