diff --git a/test/helpers/bdd-if.js b/test/helpers/bdd-if.js
new file mode 100644
index 00000000..8c120091
--- /dev/null
+++ b/test/helpers/bdd-if.js
@@ -0,0 +1,19 @@
+// Copyright IBM Corp. 2016. All Rights Reserved.
+// Node module: loopback-datasource-juggler
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+'use strict';
+
+exports.describeIf = function describeIf(cond, name, fn) {
+  if (cond)
+    describe(name, fn);
+  else
+    describe.skip(name, fn);
+};
+
+exports.itIf = function itIf(cond, name, fn) {
+  if (cond)
+    it(name, fn);
+  else
+    it.skip(name, fn);
+};
diff --git a/test/kvao.suite.js b/test/kvao.suite.js
index 895714bd..27232b45 100644
--- a/test/kvao.suite.js
+++ b/test/kvao.suite.js
@@ -1,6 +1,7 @@
 'use strict';
 
 var debug = require('debug')('test');
+var extend = require('util')._extend;
 var fs = require('fs');
 var path = require('path');
 
@@ -8,6 +9,12 @@ if (!global.Promise)
   global.Promise = require('bluebird');
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
+  connectorCapabilities = extend({
+    // Even when the backend supports millisecond precision,
+    // it's better to use intervals at least 10ms long in the tests
+    ttlPrecision: 10,
+  }, connectorCapabilities);
+
   describe('KeyValue API', function loadAllTestFiles() {
     var testRoot = path.resolve(__dirname, 'kvao');
     var testFiles = fs.readdirSync(testRoot);
diff --git a/test/kvao/expire.suite.js b/test/kvao/expire.suite.js
index 08d01979..ef72506e 100644
--- a/test/kvao/expire.suite.js
+++ b/test/kvao/expire.suite.js
@@ -1,11 +1,18 @@
 'use strict';
 
+var bdd = require('../helpers/bdd-if');
 var should = require('should');
 var helpers = require('./_helpers');
 var Promise = require('bluebird');
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
-  describe('expire', function() {
+  // While we support millisecond precision, for the purpose of tests
+  // it's better to use intervals at least 10ms long.
+  var ttlPrecision = connectorCapabilities.ttlPrecision || 10;
+
+  var canExpire = connectorCapabilities.canExpire !== false;
+
+  bdd.describeIf(canExpire, 'expire', function() {
     var CacheItem;
     beforeEach(function unpackContext() {
       CacheItem = helpers.givenCacheItem(dataSourceFactory);
@@ -14,7 +21,7 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
     it('sets key ttl - Callback API', function(done) {
       CacheItem.set('a-key', 'a-value', function(err) {
         if (err) return done(err);
-        CacheItem.expire('a-key', 1, function(err) {
+        CacheItem.expire('a-key', ttlPrecision, function(err) {
           if (err) return done(err);
           setTimeout(function() {
             CacheItem.get('a-key', function(err, value) {
@@ -22,22 +29,22 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
               should.equal(value, null);
               done();
             });
-          }, 20);
+          }, 2 * ttlPrecision);
         });
       });
     });
 
     it('sets key ttl - Promise API', function() {
       return Promise.resolve(CacheItem.set('a-key', 'a-value'))
-        .then(function() { return CacheItem.expire('a-key', 1); })
-        .delay(20)
+        .then(function() { return CacheItem.expire('a-key', ttlPrecision); })
+        .delay(2 * ttlPrecision)
         .then(function() { return CacheItem.get('a-key'); })
         .then(function(value) { should.equal(value, null); });
     });
 
     it('returns error when expiring a key that has expired', function() {
-      return Promise.resolve(CacheItem.set('expired-key', 'a-value', 1))
-        .delay(20)
+      return Promise.resolve(CacheItem.set('expired-key', 'a-value', ttlPrecision))
+        .delay(2 * ttlPrecision)
         .then(function() { return CacheItem.expire('expired-key', 1000); })
         .then(
           function() { throw new Error('expire() should have failed'); },
@@ -48,7 +55,7 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
     });
 
     it('returns error when key does not exist', function() {
-      return CacheItem.expire('key-does-not-exist', 1).then(
+      return CacheItem.expire('key-does-not-exist', ttlPrecision).then(
         function() { throw new Error('expire() should have failed'); },
         function(err) {
           err.message.should.match(/key-does-not-exist/);
diff --git a/test/kvao/get-set.suite.js b/test/kvao/get-set.suite.js
index 58307b5c..6e1481a1 100644
--- a/test/kvao/get-set.suite.js
+++ b/test/kvao/get-set.suite.js
@@ -5,6 +5,8 @@ var helpers = require('./_helpers');
 var Promise = require('bluebird');
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
+  var TTL_PRECISION = connectorCapabilities.ttlPrecision;
+
   describe('get/set', function() {
     var CacheItem;
     beforeEach(function unpackContext() {
@@ -68,8 +70,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
     });
 
     it('honours options.ttl', function() {
-      return Promise.resolve(CacheItem.set('a-key', 'a-value', { ttl: 10 }))
-      .delay(20)
+      return Promise.resolve(CacheItem.set('a-key', 'a-value', { ttl: TTL_PRECISION }))
+      .delay(2 * TTL_PRECISION)
       .then(function() { return CacheItem.get('a-key'); })
       .then(function(value) { should.equal(value, null); });
     });
@@ -79,22 +81,22 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
         return CacheItem.get('key-does-not-exist')
           .then(function(value) { should.equal(value, null); });
       });
-
-      it('converts numeric options arg to options.ttl', function() {
-        return Promise.resolve(CacheItem.set('a-key', 'a-value', 10))
-          .delay(20)
-          .then(function() { return CacheItem.get('a-key'); })
-          .then(function(value) { should.equal(value, null); });
-      });
     });
 
     describe('set', function() {
+      it('converts numeric options arg to options.ttl', function() {
+        return Promise.resolve(CacheItem.set('a-key', 'a-value', TTL_PRECISION))
+          .delay(2 * TTL_PRECISION)
+          .then(function() { return CacheItem.get('a-key'); })
+          .then(function(value) { should.equal(value, null); });
+      });
+
       it('resets TTL timer', function() {
-        return Promise.resolve(CacheItem.set('a-key', 'a-value', { ttl: 10 }))
+        return Promise.resolve(CacheItem.set('a-key', 'a-value', { ttl: TTL_PRECISION }))
           .then(function() {
             return CacheItem.set('a-key', 'another-value'); // no TTL
           })
-          .delay(20)
+          .delay(2 * TTL_PRECISION)
           .then(function() { return CacheItem.get('a-key'); })
           .then(function(value) { should.equal(value, 'another-value'); });
       });
diff --git a/test/kvao/iterate-keys.suite.js b/test/kvao/iterate-keys.suite.js
index 4036e3fa..8e4ec0a9 100644
--- a/test/kvao/iterate-keys.suite.js
+++ b/test/kvao/iterate-keys.suite.js
@@ -1,13 +1,16 @@
 'use strict';
 
 var asyncIterators = require('async-iterators');
+var bdd = require('../helpers/bdd-if');
 var helpers = require('./_helpers');
 var Promise = require('bluebird');
 var should = require('should');
 var toArray = Promise.promisify(asyncIterators.toArray);
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
-  describe('iterateKeys', function() {
+  var canIterateKeys = connectorCapabilities.canIterateKeys !== false;
+
+  bdd.describeIf(canIterateKeys, 'iterateKeys', function() {
     var CacheItem;
     beforeEach(function unpackContext() {
       CacheItem = helpers.givenCacheItem(dataSourceFactory);
diff --git a/test/kvao/keys.suite.js b/test/kvao/keys.suite.js
index adb1d474..6b197b23 100644
--- a/test/kvao/keys.suite.js
+++ b/test/kvao/keys.suite.js
@@ -1,11 +1,14 @@
 'use strict';
 
+var bdd = require('../helpers/bdd-if');
 var helpers = require('./_helpers');
 var Promise = require('bluebird');
 var should = require('should');
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
-  describe('keys', function() {
+  var canIterateKeys = connectorCapabilities.canIterateKeys !== false;
+
+  bdd.describeIf(canIterateKeys, 'keys', function() {
     var CacheItem;
     beforeEach(function unpackContext() {
       CacheItem = helpers.givenCacheItem(dataSourceFactory);
@@ -54,7 +57,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
         });
     });
 
-    it('handles large key set', function() {
+    var largeKeySets = connectorCapabilities.canIterateLargeKeySets !== false;
+    bdd.itIf(largeKeySets, 'handles large key set', function() {
       var expectedKeys = [];
       for (var ix = 0; ix < 1000; ix++)
         expectedKeys.push('key-' + ix);
diff --git a/test/kvao/ttl.suite.js b/test/kvao/ttl.suite.js
index e385c3c8..4e028f4d 100644
--- a/test/kvao/ttl.suite.js
+++ b/test/kvao/ttl.suite.js
@@ -1,11 +1,25 @@
 'use strict';
 
+var bdd = require('../helpers/bdd-if');
 var should = require('should');
 var helpers = require('./_helpers');
 var Promise = require('bluebird');
 
 module.exports = function(dataSourceFactory, connectorCapabilities) {
-  describe('ttl', function() {
+  var TTL_PRECISION = connectorCapabilities.ttlPrecision;
+
+  // Use ~1s for stores with precision of 1 ms,
+  // about 3s for stores with precision of 1s.
+  var INITIAL_TTL = Math.max(TTL_PRECISION + 1000, TTL_PRECISION * 3);
+
+  // A small delay to allow the backend to process the request, run any
+  // TTL/expire checks, etc. Use 1ms for backends supporting sub-10ms
+  // resolution to ensure the delay is not too short..
+  var SMALL_DELAY = Math.max(1, Math.floor(TTL_PRECISION / 10));
+
+  var canQueryTtl = connectorCapabilities.canQueryTtl !== false;
+
+  bdd.describeIf(canQueryTtl, 'ttl', function() {
     var CacheItem;
     beforeEach(function unpackContext() {
       CacheItem = helpers.givenCacheItem(dataSourceFactory);
@@ -14,19 +28,19 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
     it('gets TTL when key with unexpired TTL exists - Promise API',
     function() {
       return Promise.resolve(
-          CacheItem.set('a-key', 'a-value', { ttl: 1000 }))
-        .delay(1)
+          CacheItem.set('a-key', 'a-value', { ttl: INITIAL_TTL }))
+        .delay(SMALL_DELAY)
         .then(function() { return CacheItem.ttl('a-key'); })
-        .then(function(ttl) { ttl.should.be.within(1, 1000); });
+        .then(function(ttl) { ttl.should.be.within(1, INITIAL_TTL); });
     });
 
     it('gets TTL when key with unexpired TTL exists - Callback API',
     function(done) {
-      CacheItem.set('a-key', 'a-value', { ttl: 1000 }, function(err) {
+      CacheItem.set('a-key', 'a-value', { ttl: INITIAL_TTL }, function(err) {
         if (err) return done(err);
         CacheItem.ttl('a-key', function(err, ttl) {
           if (err) return done(err);
-          ttl.should.be.within(1, 1000);
+          ttl.should.be.within(1, INITIAL_TTL);
           done();
         });
       });
@@ -40,7 +54,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
 
     it('fails when getting TTL for a key with expired TTL', function() {
       return Promise.resolve(
-          CacheItem.set('expired-key', 'a-value', { ttl: 10 })).delay(20)
+          CacheItem.set('expired-key', 'a-value', { ttl: TTL_PRECISION }))
+        .delay(2 * TTL_PRECISION)
         .then(function() {
           return CacheItem.ttl('expired-key');
         })