diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1de1b61..9c1cff8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## 0.5.5 (unreleased)
  * Added function `advanceBlockTo`. ([#94](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/94))
+ * Added `expectEvent.not` support to test negative cases. ([#104](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/104))
 
 ## 0.5.4 (2019-11-13)
  * Fixed some RPC calls not having an `id` field. ([#92](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/92))
diff --git a/contracts/EventEmitter.sol b/contracts/EventEmitter.sol
index a34fd72..a0b743d 100644
--- a/contracts/EventEmitter.sol
+++ b/contracts/EventEmitter.sol
@@ -4,6 +4,7 @@ import "./IndirectEventEmitter.sol";
 
 contract EventEmitter {
     event Argumentless();
+    event WillNeverBeEmitted();
     event ShortUint(uint8 value);
     event ShortInt(int8 value);
     event LongUint(uint256 value);
diff --git a/docs/modules/ROOT/pages/api.adoc b/docs/modules/ROOT/pages/api.adoc
index 4878da5..f0d156d 100644
--- a/docs/modules/ROOT/pages/api.adoc
+++ b/docs/modules/ROOT/pages/api.adoc
@@ -161,6 +161,43 @@ async function expectEvent.inConstruction(emitter, eventName, eventArgs = {})
 
 Same as `inTransaction`, but for events emitted during the construction of `emitter`. Note that this is currently only supported for truffle contracts.
 
+=== `not.inTransaction`
+
+```javascript
+async function notInTransaction (txHash, emitter, eventName)
+```
+
+Asserts that the event with name `eventName` as declared in the `emitter` contract was not emitted in the transaction with hash `txHash`. Note that the assertion will also fail if the transaction was emitted indirectly (in a nested external function call).
+
+```javascript
+// With web3 contracts
+const contract = await MyContract.deploy().send();
+const { transactionHash } = await contract.methods.foo().send();
+await expectEvent.not.inTransaction(transactionHash, contract, 'NotEmittedByFoo');
+
+// With web3 contracts
+const contract = await MyContract.deploy().send();
+const { transactionHash } = await contract.methods.foo().send();
+await expectEvent.not.inTransaction(transactionHash, contract, 'EmittedByFoo'); // Will fail
+
+// With truffle contracts
+const contract = await MyContract.new();
+const { txHash } = await contract.foo();
+await expectEvent.not.inTransaction(txHash, contract, 'NotEmittedByFoo');
+
+const contract = await MyContract.new();
+const { txHash } = await contract.foo();
+await expectEvent.not.inTransaction(txHash, contract, 'EmittedByFoo'); // Will fail
+```
+
+=== `not.inConstruction`
+
+```javascript
+async function notInConstruction (contract, eventName)
+```
+
+Same as `not.inTransaction`, but for events emitted during the construction of emitter. Note that this is currently only supported for truffle contracts.
+
 [[expect-revert]]
 == `expectRevert`
 
diff --git a/src/expectEvent.js b/src/expectEvent.js
index a8f8594..25fda56 100644
--- a/src/expectEvent.js
+++ b/src/expectEvent.js
@@ -64,6 +64,13 @@ async function inConstruction (contract, eventName, eventArgs = {}) {
   return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs);
 }
 
+async function notInConstruction (contract, eventName) {
+  if (!isTruffleContract(contract)) {
+    throw new Error('expectEvent.inConstruction is only supported for truffle-contract objects');
+  }
+  return notInTransaction(contract.transactionHash, contract.constructor, eventName);
+}
+
 async function inTransaction (txHash, emitter, eventName, eventArgs = {}) {
   const receipt = await web3.eth.getTransactionReceipt(txHash);
 
@@ -71,6 +78,16 @@ async function inTransaction (txHash, emitter, eventName, eventArgs = {}) {
   return inLogs(logs, eventName, eventArgs);
 }
 
+async function notInTransaction (txHash, emitter, eventName) {
+  const receipt = await web3.eth.getTransactionReceipt(txHash);
+
+  const logs = decodeLogs(receipt.logs, emitter, eventName);
+
+  const events = logs.filter(e => e.event === eventName);
+
+  expect(events.length === 0).to.equal(true, `Event ${eventName} was found`);
+}
+
 // This decodes longs for a single event type, and returns a decoded object in
 // the same form truffle-contract uses on its receipts
 function decodeLogs (logs, emitter, eventName) {
@@ -138,4 +155,8 @@ function isTruffleContract (contract) {
 expectEvent.inLogs = deprecate(inLogs, 'expectEvent.inLogs() is deprecated. Use expectEvent() instead.');
 expectEvent.inConstruction = inConstruction;
 expectEvent.inTransaction = inTransaction;
+
+expectEvent.not = {};
+expectEvent.not.inConstruction = notInConstruction;
+expectEvent.not.inTransaction = notInTransaction;
 module.exports = expectEvent;
diff --git a/test/src/expectEvent.truffle.test.js b/test/src/expectEvent.truffle.test.js
index 184fc00..97b5285 100644
--- a/test/src/expectEvent.truffle.test.js
+++ b/test/src/expectEvent.truffle.test.js
@@ -458,4 +458,63 @@ contract('expectEvent (truffle contracts)', function ([deployer]) {
       });
     });
   });
+
+  describe('not', function () {
+    describe('inTransaction', function () {
+      context('with no arguments', function () {
+        beforeEach(async function () {
+          const { receipt } = await this.emitter.emitArgumentless();
+          this.txHash = receipt.transactionHash;
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event does not exist in ABI', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant'));
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless'));
+        });
+      });
+      context('with arguments', function () {
+        beforeEach(async function () {
+          this.value = 42;
+          const { receipt } = await this.emitter.emitShortUint(this.value);
+          this.txHash = receipt.transactionHash;
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint'));
+        });
+      });
+      context('with events emitted by an indirectly called contract', function () {
+        beforeEach(async function () {
+          this.value = 'OpenZeppelin';
+          const { receipt } = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address);
+          this.txHash = receipt.transactionHash;
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString'));
+        });
+      });
+    });
+    describe('inConstructor', function () {
+      it('accepts not emitted events', async function () {
+        await expectEvent.not.inConstruction(this.emitter, 'WillNeverBeEmitted');
+      });
+      it('throws when event does not exist in ABI', async function () {
+        await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Nonexistant'));
+      });
+      it('throws when event its emitted', async function () {
+        await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint'));
+        await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Boolean'));
+        await assertFailure(expectEvent.not.inConstruction(this.emitter, 'String'));
+      });
+    });
+  });
 });
diff --git a/test/src/expectEvent.web3.test.js b/test/src/expectEvent.web3.test.js
index 6e1b7ae..2ea8304 100644
--- a/test/src/expectEvent.web3.test.js
+++ b/test/src/expectEvent.web3.test.js
@@ -430,4 +430,54 @@ contract('expectEvent (web3 contracts) ', function ([deployer]) {
       await assertFailure(expectEvent.inConstruction(this.emitter, 'ShortUint'));
     });
   });
+
+  describe('not', function () {
+    describe('inTransaction', function () {
+      context('with no arguments', function () {
+        beforeEach(async function () {
+          ({ transactionHash: this.txHash } = await this.emitter.methods.emitArgumentless().send());
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event does not exist in ABI', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant'));
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless'));
+        });
+      });
+      context('with arguments', function () {
+        beforeEach(async function () {
+          this.value = 42;
+          ({ transactionHash: this.txHash } = await this.emitter.methods.emitShortUint(this.value).send());
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint'));
+        });
+      });
+      context('with events emitted by an indirectly called contract', function () {
+        beforeEach(async function () {
+          this.value = 'OpenZeppelin';
+          ({ transactionHash: this.txHash } = await this.emitter.methods.emitStringAndEmitIndirectly(
+            this.value, this.secondEmitter.options.address
+          ).send());
+        });
+        it('accepts not emitted events', async function () {
+          await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
+        });
+        it('throws when event its emitted', async function () {
+          await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString'));
+        });
+      });
+    });
+    describe('inConstruction', function () {
+      it('is unsupported', async function () {
+        await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint'));
+      });
+    });
+  });
 });