diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index e011858ac..8c17bb1b0 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -255,7 +255,8 @@ class Scratch3LooksBlocks {
         if (typeof message === 'number') {
             message = message.toFixed(2);
         }
-        this._updateBubble(util.target, 'say', String(args.MESSAGE));
+        message = String(message);
+        this.runtime.emit('SAY', util.target, 'say', message);
     }
 
     sayforsecs (args, util) {
diff --git a/test/unit/blocks_looks.js b/test/unit/blocks_looks.js
index 647aeb17a..6c1877c8a 100644
--- a/test/unit/blocks_looks.js
+++ b/test/unit/blocks_looks.js
@@ -1,5 +1,6 @@
 const test = require('tap').test;
 const Looks = require('../../src/blocks/scratch3_looks');
+const Runtime = require('../../src/engine/runtime');
 const util = {
     target: {
         currentCostume: 0, // Internally, current costume is 0 indexed
@@ -52,11 +53,18 @@ test('getBackdropNumberName can return costume name', t => {
 });
 
 test('numbers should be rounded to two decimals in say', t => {
+    const rt = new Runtime();
+    const looks = new Looks(rt);
+
     const args = {MESSAGE: 3.14159};
-    const sayString = blocks.say(args, util);
-    // This breaks becuase it is not returned,
-    // instead this calls this._updateBubble(util.target, 'say', String(args.MESSAGE));
-    // I'm not familiar
-    t.strictEqual(sayString, '3.14');
-    t.end();
+    const expectedSayString = '3.14';
+
+    rt.removeAllListeners('SAY'); // Prevent say blocks from executing
+
+    rt.addListener('SAY', (target, type, sayString) => {
+        t.strictEqual(sayString, expectedSayString);
+        t.end();
+    });
+
+    looks.say(args, util);
 });