diff --git a/src/SpecHelper.js b/src/SpecHelper.js
new file mode 100644
index 0000000000000000000000000000000000000000..a63bf43346e5b952b1a32947cef4706f72593a73
--- /dev/null
+++ b/src/SpecHelper.js
@@ -0,0 +1,291 @@
+/* eslint-env browser, jasmine */
+
+/*
+  This is just a compilation of functions that help to test this library!
+*/
+
+// When testing, you store everything on the global object. We call it g
+
+var Y = require('./y.js')
+module.exports = Y
+
+var g
+if (typeof global !== 'undefined') {
+  g = global
+} else if (typeof window !== 'undefined') {
+  g = window
+} else {
+  throw new Error('No global object?')
+}
+g.g = g
+
+g.YConcurrency_TestingMode = true
+
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000
+
+g.describeManyTimes = function describeManyTimes (times, name, f) {
+  for (var i = 0; i < times; i++) {
+    describe(name, f)
+  }
+}
+
+/*
+  Wait for a specified amount of time (in ms). defaults to 5ms
+*/
+function wait (t) {
+  if (t == null) {
+    t = 5
+  }
+  return new Promise(function (resolve) {
+    setTimeout(function () {
+      resolve()
+    }, t * 3)
+  })
+}
+g.wait = wait
+
+g.databases = ['Memory']
+if (typeof window !== 'undefined') {
+  g.databases.push('IndexedDB')
+}
+
+/*
+  returns a random element of o.
+  works on Object, and Array
+*/
+function getRandom (o) {
+  if (o instanceof Array) {
+    return o[Math.floor(Math.random() * o.length)]
+  } else if (o.constructor === Object) {
+    var ks = []
+    for (var key in o) {
+      ks.push(key)
+    }
+    return o[getRandom(ks)]
+  }
+}
+g.getRandom = getRandom
+
+function getRandomNumber (n) {
+  if (n == null) {
+    n = 9999
+  }
+  return Math.floor(Math.random() * n)
+}
+g.getRandomNumber = getRandomNumber
+
+function * applyTransactions (relAmount, numberOfTransactions, objects, users, transactions) {
+  function randomTransaction (root) {
+    var f = getRandom(transactions)
+    f(root)
+  }
+  for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) {
+    var r = Math.random()
+    if (r >= 0.5) {
+      // 50% chance to flush
+      users[0].connector.flushOne() // flushes for some user.. (not necessarily 0)
+    } else if (r >= 0.05) {
+      // 45% chance to create operation
+      randomTransaction(getRandom(objects))
+    } else {
+      // 5% chance to disconnect/reconnect
+      var u = getRandom(users)
+      if (u.connector.isDisconnected()) {
+        yield u.reconnect()
+      } else {
+        yield u.disconnect()
+      }
+    }
+    yield wait()
+  }
+}
+
+g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
+  yield* applyTransactions(1, numberOfTransactions, objects, users, transactions)
+  yield users[0].connector.flushAll()
+  yield wait()
+  for (var u in users) {
+    yield users[u].reconnect()
+  }
+  yield wait(100)
+  yield users[0].connector.flushAll()
+  yield g.garbageCollectAllUsers(users)
+})
+
+g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
+  yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
+  yield users[0].connector.flushAll()
+  yield g.garbageCollectAllUsers(users)
+  yield wait(100)
+  for (var u in users) {
+    // TODO: here, we enforce that two users never sync at the same time with u[0]
+    //       enforce that in the connector itself!
+    yield users[u].reconnect()
+  }
+  yield wait(100)
+  yield users[0].connector.flushAll()
+  yield wait(100)
+  yield g.garbageCollectAllUsers(users)
+})
+
+g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
+  // gc two times because of the two gc phases (really collect everything)
+  yield wait(100)
+  for (var i in users) {
+    yield users[i].db.garbageCollect()
+    yield users[i].db.garbageCollect()
+  }
+  yield wait(100)
+})
+
+g.compareAllUsers = async(function * compareAllUsers (users) {
+  var s1, s2 // state sets
+  var ds1, ds2 // delete sets
+  var allDels1, allDels2 // all deletions
+  var db1 = [] // operation store of user1
+
+  // t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
+  function * t1 () {
+    s1 = yield* this.getStateSet()
+    ds1 = yield* this.getDeleteSet()
+    allDels1 = []
+    yield* this.ds.iterate(this, null, null, function * (d) {
+      allDels1.push(d)
+    })
+  }
+  function * t2 () {
+    s2 = yield* this.getStateSet()
+    ds2 = yield* this.getDeleteSet()
+    allDels2 = []
+    yield* this.ds.iterate(this, null, null, function * (d) {
+      allDels2.push(d)
+    })
+  }
+  yield users[0].connector.flushAll()
+  yield wait()
+  yield g.garbageCollectAllUsers(users)
+
+  for (var uid = 0; uid < users.length; uid++) {
+    var u = users[uid]
+    u.db.requestTransaction(function * () {
+      // compare deleted ops against deleteStore
+      yield* this.os.iterate(this, null, null, function * (o) {
+        if (o.deleted === true) {
+          expect(yield* this.isDeleted(o.id)).toBeTruthy()
+        }
+      })
+      // compare deleteStore against deleted ops
+      var ds = []
+      yield* this.ds.iterate(this, null, null, function * (d) {
+        ds.push(d)
+      })
+      for (var j in ds) {
+        var d = ds[j]
+        for (var i = 0; i < d.len; i++) {
+          var o = yield* this.getOperation([d.id[0], d.id[1] + i])
+          // gc'd or deleted
+          if (d.gc) {
+            expect(o).toBeFalsy()
+          } else {
+            expect(o.deleted).toBeTruthy()
+          }
+        }
+      }
+    })
+    // compare allDels tree
+    yield wait()
+    if (s1 == null) {
+      u.db.requestTransaction(function * () {
+        yield* t1.call(this)
+        yield* this.os.iterate(this, null, null, function * (o) {
+          o = Y.utils.copyObject(o)
+          delete o.origin
+          db1.push(o)
+        })
+      })
+      yield wait()
+    } else {
+      // TODO: make requestTransaction return a promise..
+      u.db.requestTransaction(function * () {
+        yield* t2.call(this)
+        expect(s1).toEqual(s2)
+        expect(allDels1).toEqual(allDels2) // inner structure
+        expect(ds1).toEqual(ds2) // exported structure
+        var count = 0
+        yield* this.os.iterate(this, null, null, function * (o) {
+          o = Y.utils.copyObject(o)
+          delete o.origin
+          expect(db1[count++]).toEqual(o)
+        })
+      })
+      yield wait()
+    }
+  }
+})
+
+g.createUsers = async(function * createUsers (self, numberOfUsers, database) {
+  if (Y.utils.globalRoom.users[0] != null) {
+    yield Y.utils.globalRoom.users[0].flushAll()
+  }
+  // destroy old users
+  for (var u in Y.utils.globalRoom.users) {
+    Y.utils.globalRoom.users[u].y.destroy()
+  }
+  self.users = null
+
+  var promises = []
+  for (var i = 0; i < numberOfUsers; i++) {
+    promises.push(Y({
+      db: {
+        name: database,
+        namespace: 'User ' + i,
+        cleanStart: true,
+        gcTimeout: -1
+      },
+      connector: {
+        name: 'Test',
+        debug: false
+      }
+    }))
+  }
+  self.users = yield Promise.all(promises)
+  return self.users
+})
+
+/*
+  Until async/await arrives in js, we use this function to wait for promises
+  by yielding them.
+*/
+function async (makeGenerator) {
+  return function (arg) {
+    var generator = makeGenerator.apply(this, arguments)
+
+    function handle (result) {
+      if (result.done) return Promise.resolve(result.value)
+
+      return Promise.resolve(result.value).then(function (res) {
+        return handle(generator.next(res))
+      }, function (err) {
+        return handle(generator.throw(err))
+      })
+    }
+    try {
+      return handle(generator.next())
+    } catch (ex) {
+      generator.throw(ex)
+      // return Promise.reject(ex)
+    }
+  }
+}
+g.async = async
+
+function logUsers (self) {
+  if (self.constructor === Array) {
+    self = {users: self}
+  }
+  self.users[0].db.logTable()
+  self.users[1].db.logTable()
+  self.users[2].db.logTable()
+}
+
+g.logUsers = logUsers