Repository: wix/Detox
Pull request #2235 (2020-07-28 14:36:29)

Apply some post-processing over the multi ADB-name PR
- [ ] This is a small change 
- [ ] This change has been discussed in issue #<?> and the solution has been agreed upon with maintainers.

---

**Description:**

This is a direct continuation of #2169. Mostly, but not solely, focused on improving unit tests and coverage rate.

coveralls: ## Pull Request Test Coverage Report for [Build 2508](https://coveralls.io/builds/32374307)

* **23** of **23**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.2%**) to **85.82%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/32374307/badge)](https://coveralls.io/builds/32374307) |
| :-- | --: |
| Change from base [Build 1319](https://coveralls.io/builds/32371129): |  0.2% |
| Covered Lines: | 3643 |
| Relevant Lines: | 4084 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/detox/package.json b/detox/package.json
index 8352134bb..a0358d163 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -101,8 +101,6 @@
       "src/utils/sleep.js",
       "AAPT.js",
       "ADB.js",
-      "DeviceHandle.js",
-      "EmulatorHandle.js",
       "fsext.js",
       "debug.js",
       "src/ios/earlgreyapi",
diff --git a/detox/src/devices/drivers/android/exec/ADB.js b/detox/src/devices/drivers/android/exec/ADB.js
index 8943c487f..e72482588 100644
--- a/detox/src/devices/drivers/android/exec/ADB.js
+++ b/detox/src/devices/drivers/android/exec/ADB.js
@@ -3,7 +3,6 @@ const {execWithRetriesAndLogs, spawnAndLog} = require('../../../../utils/exec');
 const {escape} = require('../../../../utils/pipeCommands');
 const DetoxRuntimeError = require('../../../../errors/DetoxRuntimeError');
 const {getAdbPath} = require('../../../../utils/environment');
-const {encodeBase64} = require('../../../../utils/encoding');
 const DeviceHandle = require('../tools/DeviceHandle');
 const EmulatorHandle = require('../tools/EmulatorHandle');
 
@@ -24,13 +23,6 @@ class ADB {
         ? new EmulatorHandle(s)
         : new DeviceHandle(s))
       .value();
-
-    for (const device of devices) {
-      if (device.type === 'emulator') {
-        assertEmulatorHasPort(device, stdout);
-      }
-    }
-
     return { devices, stdout };
   }
 
@@ -297,22 +289,4 @@ class ADB {
   }
 }
 
-function assertEmulatorHasPort(device, stdout) {
-  if (device.port) {
-    return;
-  }
-
-  const errorMessage = [
-    `Failed to determine telnet port for emulator device '${device.adbName}'!`,
-    `Please help us out by reporting dump below in: https://github.com/wix/Detox/issues/1427`,
-    `------ BEGIN DUMP ------`,
-    `adb devices base64: ${encodeBase64(stdout)}`,
-    `adb devices: ${stdout}`,
-    `port: ${device.port}`,
-    `------  END DUMP  ------`,
-  ].join('\n');
-
-  throw new Error(errorMessage);
-}
-
 module.exports = ADB;
diff --git a/detox/src/devices/drivers/android/tools/EmulatorHandle.js b/detox/src/devices/drivers/android/tools/EmulatorHandle.js
index 1a829a20f..7b9f401c1 100644
--- a/detox/src/devices/drivers/android/tools/EmulatorHandle.js
+++ b/detox/src/devices/drivers/android/tools/EmulatorHandle.js
@@ -4,26 +4,24 @@ const EmulatorTelnet = require('./EmulatorTelnet');
 class EmulatorHandle extends DeviceHandle {
   constructor(deviceString) {
     super(deviceString);
+    this._telnet = new EmulatorTelnet();
 
     this.port = this.adbName.split('-')[1];
   }
 
-  queryName() {
-    if (!this._name) {
-      this._name = this._queryNameViaTelnet();
+  /* async */ queryName() {
+    if (!this._namePromise) {
+      this._namePromise = this._queryNameViaTelnet();
     }
-
-    return this._name;
+    return this._namePromise;
   }
 
   async _queryNameViaTelnet() {
-    const telnet = new EmulatorTelnet();
-
-    await telnet.connect(this.port);
+    await this._telnet.connect(this.port);
     try {
-      return await telnet.avdName();
+      return await this._telnet.avdName();
     } finally {
-      await telnet.quit();
+      await this._telnet.quit();
     }
   }
 }
diff --git a/detox/src/devices/drivers/android/tools/FreeDeviceFinder.js b/detox/src/devices/drivers/android/tools/FreeDeviceFinder.js
index 70a3874fc..1071e2f95 100644
--- a/detox/src/devices/drivers/android/tools/FreeDeviceFinder.js
+++ b/detox/src/devices/drivers/android/tools/FreeDeviceFinder.js
@@ -11,28 +11,39 @@ class FreeDeviceFinder {
   async findFreeDevice(deviceQuery) {
     const { devices } = await this.adb.devices();
     for (const candidate of devices) {
-      const isBusy = this.deviceRegistry.isDeviceBusy(candidate.adbName);
-      if (isBusy) {
-        log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${candidate.adbName} is busy, skipping...`);
-        continue;
+      if (await this._isDeviceFreeAndMatching(candidate, deviceQuery)) {
+        return candidate.adbName;
       }
+    }
+    return null;
+  }
 
-      const isOffline = candidate.status === 'offline';
-      if (isOffline) {
-        log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${candidate.adbName} is offline, skipping...`);
-        continue;
-      }
+  /**
+   * @protected
+   */
+  async _isDeviceFreeAndMatching(candidate, deviceQuery) {
+    const { adbName } = candidate;
 
-      const isMatching = await this._isDeviceMatching(candidate, deviceQuery);
-      if (isMatching) {
-        log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Found a matching free device ${candidate.adbName}`);
-        return candidate.adbName;
-      } else {
-        log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${candidate.adbName} does not match "${deviceQuery}"`);
-      }
+    const isBusy = this.deviceRegistry.isDeviceBusy(adbName);
+    if (isBusy) {
+      log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is busy, skipping...`);
+      return false;
     }
 
-    return null;
+    const isOffline = candidate.status === 'offline';
+    if (isOffline) {
+      log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is offline, skipping...`);
+      return false;
+    }
+
+    const isMatching = await this._isDeviceMatching(candidate, deviceQuery);
+    if (!isMatching) {
+      log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} does not match "${deviceQuery}"`);
+      return false;
+    }
+
+    log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Found a matching & free device ${candidate.adbName}`);
+    return true;
   }
 
   /**
diff --git a/detox/src/devices/drivers/android/tools/__mocks__/handles.js b/detox/src/devices/drivers/android/tools/__mocks__/handles.js
index 82111b37f..fb81c2579 100644
--- a/detox/src/devices/drivers/android/tools/__mocks__/handles.js
+++ b/detox/src/devices/drivers/android/tools/__mocks__/handles.js
@@ -1,22 +1,21 @@
-const DeviceHandle = require('../DeviceHandle');
-const EmulatorHandle = require('../EmulatorHandle');
-
 const mockAvdName = 'mockAvdName';
-class MockEmulatorHandle extends EmulatorHandle {
-  queryName() {
-    return Promise.resolve(mockAvdName);
-  }
-}
 
-const emulator5556 = new MockEmulatorHandle('emulator-5556\tdevice');
-const localhost5555 = new DeviceHandle('localhost:5555\tdevice');
-const ip5557 = new DeviceHandle('192.168.4.19:5557\tdevice');
-const deviceOffline = new MockEmulatorHandle('192.168.4.19:5559\toffline');
+const aDeviceHandle = (adbName, status = 'device') => ({
+  adbName,
+  status,
+  type: 'device',
+});
+
+const anEmulatorHandle = (adbName, status) => ({
+  ...aDeviceHandle(adbName, status),
+  type: 'emulator',
+  queryName: () => Promise.resolve(mockAvdName),
+});
 
 module.exports = {
   mockAvdName,
-  emulator5556,
-  localhost5555,
-  ip5557,
-  deviceOffline,
+  emulator5556: anEmulatorHandle('emulator-5556'),
+  localhost5555: aDeviceHandle('localhost:5555'),
+  ip5557: aDeviceHandle('192.168.4.19:5557'),
+  deviceOffline: aDeviceHandle('192.168.4.19:5559', 'offline'),
 };


Test patch:
diff --git a/detox/src/devices/drivers/android/AndroidDriver.test.js b/detox/src/devices/drivers/android/AndroidDriver.test.js
index 8ea54506f..f5e9e5d74 100644
--- a/detox/src/devices/drivers/android/AndroidDriver.test.js
+++ b/detox/src/devices/drivers/android/AndroidDriver.test.js
@@ -1,5 +1,7 @@
 const _ = require('lodash');
 
+const latestInstanceOf = (clazz) => _.last(clazz.mock.instances);
+
 describe('Android driver', () => {
   const deviceId = 'device-id-mock';
   const bundleId = 'bundle-id-mock';
@@ -20,12 +22,14 @@ describe('Android driver', () => {
   let AAPTClass;
   let FileXferClass;
   let AppInstallHelperClass;
+  let DeviceRegistryClass;
 
-  const adbObj = () => _.last(ADBClass.mock.instances);
-  const aaptObj = () => _.last(AAPTClass.mock.instances);
-  const fileXferObj = () => _.last(FileXferClass.mock.instances);
-  const appInstallHelperObj = () => _.last(AppInstallHelperClass.mock.instances);
-  const instrumentationObj = () => _.last(InstrumentationClass.mock.instances);
+  const adbObj = () => latestInstanceOf(ADBClass);
+  const aaptObj = () => latestInstanceOf(AAPTClass);
+  const fileXferObj = () => latestInstanceOf(FileXferClass);
+  const appInstallHelperObj = () => latestInstanceOf(AppInstallHelperClass);
+  const instrumentationObj = () => latestInstanceOf(InstrumentationClass);
+  const deviceRegistryObj = () => latestInstanceOf(DeviceRegistryClass);
 
   let uut;
   beforeEach(() => {
@@ -131,7 +135,7 @@ describe('Android driver', () => {
       expect(emitter.off).toHaveBeenCalled());
 
     it('should dispose device', () => {
-      expect(mockDisposeDevice).toHaveBeenCalledWith(deviceId);
+      expect(deviceRegistryObj().disposeDevice).toHaveBeenCalledWith(deviceId);
     });
   });
 
@@ -467,7 +471,6 @@ describe('Android driver', () => {
     });
   });
 
-  const mockDisposeDevice = jest.fn();
   const setUpModuleDepMocks = () => {
     jest.mock('fs', () => ({
       existsSync: jest.fn(),
@@ -516,12 +519,6 @@ describe('Android driver', () => {
 
     const InvocationManager = jest.genMockFromModule('../../../invoke').InvocationManager;
     invocationManager = new InvocationManager();
-
-    jest.mock('../../DeviceRegistry', () => class {
-      constructor() {
-        this.disposeDevice = mockDisposeDevice;
-      }
-    });
   };
 
   const setUpClassDepMocks = () => {
@@ -534,7 +531,7 @@ describe('Android driver', () => {
     jest.mock('./exec/ADB');
     ADBClass = require('./exec/ADB');
     ADBClass.mockImplementation(() => {
-      const _this = _.last(ADBClass.mock.instances);
+      const _this = latestInstanceOf(ADBClass);
       _this.adbBin = 'ADB binary mock';
       _this.spawnInstrumentation.mockReturnValue({
         childProcess: {
@@ -553,12 +550,15 @@ describe('Android driver', () => {
     jest.mock('./tools/TempFileXfer');
     FileXferClass = require('./tools/TempFileXfer');
     FileXferClass.mockImplementation(() => {
-      const _this = _.last(FileXferClass.mock.instances);
+      const _this = latestInstanceOf(FileXferClass);
       _this.send.mockResolvedValue(mockNotificationDataTargetPath)
     });
 
     jest.mock('./tools/AppInstallHelper');
     AppInstallHelperClass = require('./tools/AppInstallHelper');
+
+    jest.mock('../../DeviceRegistry');
+    DeviceRegistryClass = require('../../DeviceRegistry');
   };
 
   const mockGetAbsoluteBinaryPathImpl = (x) => `absolutePathOf(${x})`;
diff --git a/detox/src/devices/drivers/android/emulator/FreeEmulatorFinder.test.js b/detox/src/devices/drivers/android/emulator/FreeEmulatorFinder.test.js
index 3acf20771..79ab72225 100644
--- a/detox/src/devices/drivers/android/emulator/FreeEmulatorFinder.test.js
+++ b/detox/src/devices/drivers/android/emulator/FreeEmulatorFinder.test.js
@@ -1,20 +1,27 @@
-const FreeEmulatorFinder = require('./FreeEmulatorFinder');
 const { mockAvdName, emulator5556, localhost5555 } = require('../tools/__mocks__/handles');
 
 describe('FreeEmulatorFinder', () => {
   const mockAdb = { devices: jest.fn() };
 
+  let mockDeviceRegistry;
   let uut;
   beforeEach(() => {
-    mockAdbDevices([emulator5556]);
-    uut = new FreeEmulatorFinder(mockAdb, { isDeviceBusy: jest.fn().mockReturnValue(false) });
+    const DeviceRegistry = jest.genMockFromModule('../../../DeviceRegistry');
+    mockDeviceRegistry = new DeviceRegistry();
+    mockDeviceRegistry.isDeviceBusy.mockReturnValue(false);
+
+    const FreeEmulatorFinder = require('./FreeEmulatorFinder');
+    uut = new FreeEmulatorFinder(mockAdb, mockDeviceRegistry);
   });
 
   it('should return device when it is an emulator and avdName matches', async () => {
-    expect(await uut.findFreeDevice(mockAvdName)).toBe(emulator5556.adbName);
+    mockAdbDevices([emulator5556]);
+    const result = await uut.findFreeDevice(mockAvdName);
+    expect(result).toBe(emulator5556.adbName);
   });
 
   it('should return null when avdName does not match', async () => {
+    mockAdbDevices([emulator5556]);
     expect(await uut.findFreeDevice('wrongAvdName')).toBe(null);
   });
 
diff --git a/detox/src/devices/drivers/android/exec/ADB.test.js b/detox/src/devices/drivers/android/exec/ADB.test.js
index c8fc5fa6c..4df29e28d 100644
--- a/detox/src/devices/drivers/android/exec/ADB.test.js
+++ b/detox/src/devices/drivers/android/exec/ADB.test.js
@@ -1,9 +1,10 @@
 describe('ADB', () => {
   const adbBinPath = `/Android/sdk-mock/platform-tools/adb`;
 
-  let mockEmulatorTelnet;
   let ADB;
   let adb;
+  let DeviceHandle;
+  let EmulatorHandle;
   let exec;
   let spawn;
 
@@ -16,19 +17,11 @@ describe('ADB', () => {
       encodeBase64: (text) => `base64(${text})`,
     }));
 
-    mockEmulatorTelnet = {
-      connect: jest.fn(),
-      quit: jest.fn(),
-      avdName: jest.fn(),
-    };
-    class MockEmulatorTelnet {
-      constructor() {
-        this.connect = mockEmulatorTelnet.connect;
-        this.quit = mockEmulatorTelnet.quit;
-        this.avdName = mockEmulatorTelnet.avdName;
-      }
-    }
-    jest.mock('../tools/EmulatorTelnet', () => MockEmulatorTelnet);
+    jest.mock('../tools/DeviceHandle');
+    DeviceHandle = require('../tools/DeviceHandle');
+
+    jest.mock('../tools/EmulatorHandle');
+    EmulatorHandle = require('../tools/EmulatorHandle');
 
     jest.mock('../../../../utils/exec', () => ({
       execWithRetriesAndLogs: jest.fn().mockReturnValue({ stdout: '' }),
@@ -42,57 +35,37 @@ describe('ADB', () => {
   });
 
   describe('devices', () => {
+    const mockDevices = [
+      'MOCK_SERIAL\tdevice',
+      '192.168.60.101:6666\tdevice',
+      'emulator-5554\tdevice',
+      'emulator-5556\toffline',
+    ];
+    const adbDevices = ['List of devices attached', ...mockDevices, ''].join('\n');
+
     it(`should invoke ADB`, async () => {
       await adb.devices();
       expect(exec).toHaveBeenCalledWith(`"${adbBinPath}"  devices`, { verbosity: 'high' }, undefined, 1);
       expect(exec).toHaveBeenCalledTimes(1);
     });
 
-    it('should query device name lazily', async () => {
-      const adbDevices = 'List of devices attached\n'
-        + 'MOCK_SERIAL\tdevice\n'
-        + '192.168.60.101:6666\tdevice\n'
-        + 'emulator-5554\tdevice\n'
-        + 'emulator-5556\toffline\n'
-        + '\n';
-
-
+    it('should return proper, type-based device handles', async () => {
       exec.mockReturnValue({ stdout: adbDevices });
 
       const { devices, stdout } = await adb.devices();
+
       expect(stdout).toBe(adbDevices);
       expect(devices).toHaveLength(4);
-
       expect(devices).toEqual([
-        { type: 'device', adbName: 'MOCK_SERIAL', status: 'device' },
-        { type: 'genymotion', adbName: '192.168.60.101:6666', status: 'device' },
-        { type: 'emulator', adbName: 'emulator-5554', port: '5554', status: 'device' },
-        { type: 'emulator', adbName: 'emulator-5556', port: '5556', status: 'offline' },
-      ]);
-
-      mockEmulatorTelnet.avdName.mockReturnValue('Nexus_5X_API_29_x86');
-      expect(await devices[2].queryName()).toBe('Nexus_5X_API_29_x86');
-      expect(mockEmulatorTelnet.connect).toHaveBeenCalledWith('5554');
-    });
-
-    it(`Parse 'adb device' output with devices of all kinds`, async () => {
-      const adbDevicesConsoleOutput = "List of devices attached\n"
-        + "192.168.60.101:5555\tdevice\n"
-        + "emulator-5556\tdevice\n"
-        + "emulator-5554\tdevice\n"
-        + "sx432wsds\tdevice\n"
-        + "\n";
-      exec.mockReturnValue({
-        stdout: adbDevicesConsoleOutput
-      });
-
-      const { devices } = await adb.devices();
-      expect(devices).toEqual([
-        { "adbName": "192.168.60.101:5555", "type": "genymotion", status: "device" },
-        { "adbName": "emulator-5556", "port": "5556", "type": "emulator", status: "device" },
-        { "adbName": "emulator-5554", "port": "5554", "type": "emulator", status: "device" },
-        { "adbName": "sx432wsds", "type": "device", status: "device" }
+        DeviceHandle.mock.instances[0],
+        DeviceHandle.mock.instances[1],
+        EmulatorHandle.mock.instances[0],
+        EmulatorHandle.mock.instances[1],
       ]);
+      expect(DeviceHandle).toHaveBeenCalledWith(mockDevices[0]);
+      expect(DeviceHandle).toHaveBeenCalledWith(mockDevices[1]);
+      expect(EmulatorHandle).toHaveBeenCalledWith(mockDevices[2]);
+      expect(EmulatorHandle).toHaveBeenCalledWith(mockDevices[3]);
     });
 
     it(`should return an empty list if no devices are available`, async () => {
@@ -103,31 +76,6 @@ describe('ADB', () => {
       const {devices} = await adb.devices();
       expect(devices.length).toEqual(0);
     });
-
-    it(`should abort if port can't be parsed`, async () => {
-      const adbDevicesResult = 'List of devices attached\nemulator-\tdevice\n';
-      exec.mockReturnValue({
-        stdout: adbDevicesResult
-      });
-
-      try {
-        await adb.devices();
-        fail('Expected an error');
-      } catch (error) {
-        expect(mockEmulatorTelnet.connect).not.toHaveBeenCalled();
-        expect(error.message).toContain(`Failed to determine telnet port for emulator device 'emulator-'!`);
-        expect(error.message).toContain(`base64(${adbDevicesResult})`);
-      }
-    });
-
-    it(`should skip telnet if no devices are available`, async () => {
-      exec.mockReturnValue({
-        stdout: 'List of devices attached\n'
-      });
-
-      await adb.devices();
-      expect(mockEmulatorTelnet.connect).not.toHaveBeenCalled();
-    });
   });
 
   it(`install`, async () => {
diff --git a/detox/src/devices/drivers/android/tools/DeviceHandle.test.js b/detox/src/devices/drivers/android/tools/DeviceHandle.test.js
new file mode 100644
index 000000000..6ca5fed86
--- /dev/null
+++ b/detox/src/devices/drivers/android/tools/DeviceHandle.test.js
@@ -0,0 +1,32 @@
+describe('Device-handle', () => {
+
+  let DeviceHandle;
+  beforeEach(() => {
+    DeviceHandle = require('./DeviceHandle');
+  });
+
+  const deviceHandle = (adbName, status) => new DeviceHandle(`${adbName}\t${status}`);
+
+  it('should extract the device\'s adb name', () => {
+    const uut = deviceHandle('mockdevice', 'online');
+    expect(uut.adbName).toEqual('mockdevice');
+  });
+
+  it('should extract the device status', () => {
+    const uut = deviceHandle('mockdevice', 'mockstatus');
+    expect(uut.status).toEqual('mockstatus');
+  });
+
+  it('should deduce: device-type = emulator', () => {
+    expect(deviceHandle('emulator-5554', 'mockstatus').type).toEqual('emulator');
+  });
+
+  it('should deduce: device-type = genymotion', () => {
+    expect(deviceHandle('127.0.0.1:1234', 'mockstatus').type).toEqual('genymotion');
+    expect(deviceHandle('192.168.60.101:6666', 'mockstatus').type).toEqual('genymotion');
+  });
+
+  it('should deduce: device-type = device, as the default', () => {
+    expect(deviceHandle('MOCK_SERIAL', 'mockstatus').type).toEqual('device');
+  });
+});
diff --git a/detox/src/devices/drivers/android/tools/EmulatorHandle.test.js b/detox/src/devices/drivers/android/tools/EmulatorHandle.test.js
new file mode 100644
index 000000000..a2346a774
--- /dev/null
+++ b/detox/src/devices/drivers/android/tools/EmulatorHandle.test.js
@@ -0,0 +1,79 @@
+describe('Emulator handle', () => {
+
+  let EmulatorHandle;
+  let EmulatorTelnet;
+  beforeEach(() => {
+    jest.mock('./EmulatorTelnet');
+    EmulatorTelnet = require('./EmulatorTelnet');
+    EmulatorHandle = require('./EmulatorHandle');
+  });
+
+  const emulatorHandle = (adbName, status) => new EmulatorHandle(`${adbName}\t${status}`);
+
+  it('should extract the name and status', () => {
+    const uut = emulatorHandle('emulator-6667', 'mock-status');
+    expect(uut.adbName).toEqual('emulator-6667');
+    expect(uut.status).toEqual('mock-status');
+  });
+
+  it('should extract the emulator\'s port', () => {
+    const uut = emulatorHandle('emulator-1234', 'offline');
+    expect(uut.port).toEqual('1234');
+  });
+
+  describe('Device name querying via telnet', () => {
+    let uut;
+    let telnet;
+    beforeEach(() => {
+      uut = emulatorHandle('emulator-2345', 'offline');
+      telnet = EmulatorTelnet.mock.instances[0];
+    });
+
+    it('should telnet-connect', async () => {
+      await uut.queryName();
+      expect(telnet.connect).toHaveBeenCalledWith('2345');
+    });
+
+    it('should fail if telnet-connect fails', async () => {
+      telnet.connect.mockRejectedValue(new Error('mock telnet error'));
+      try {
+        await uut.queryName();
+        fail('Expected an error');
+      } catch (e) {}
+    });
+
+    it('should return name from telnet command', async () => {
+      const avdName = 'mock AVD';
+      telnet.avdName.mockReturnValue(avdName);
+
+      const result = await uut.queryName();
+      expect(result).toEqual(avdName);
+    });
+
+    it('should close telnet when done', async () => {
+      await uut.queryName();
+      expect(telnet.quit).toHaveBeenCalled();
+    });
+
+    it('should close telnet even if name resolution fails', async () => {
+      telnet.avdName.mockRejectedValue(new Error('mock name resolution error'));
+      try {
+        await uut.queryName();
+        fail('Expected an error');
+      } catch (e) {}
+      expect(telnet.quit).toHaveBeenCalled();
+    });
+
+    it('should only query once', async () => {
+      const avdName = 'mock AVD';
+      telnet.avdName
+        .mockReturnValueOnce(avdName)
+        .mockReturnValueOnce('some other name');
+
+      await uut.queryName();
+      const result = await uut.queryName();
+      expect(result).toEqual(avdName);
+      expect(telnet.avdName).toHaveBeenCalledTimes(1);
+    });
+  });
+});
diff --git a/detox/src/devices/drivers/android/tools/FreeDeviceFinder.test.js b/detox/src/devices/drivers/android/tools/FreeDeviceFinder.test.js
index b9dca76d2..adc42eb06 100644
--- a/detox/src/devices/drivers/android/tools/FreeDeviceFinder.test.js
+++ b/detox/src/devices/drivers/android/tools/FreeDeviceFinder.test.js
@@ -7,22 +7,31 @@ describe('FreeDeviceFinder', () => {
   let mockDeviceRegistry;
   let uut;
   beforeEach(() => {
-    mockAdbDevices([emulator5556]);
-    mockDeviceRegistry = { isDeviceBusy: jest.fn().mockReturnValue(false) };
+    const DeviceRegistry = jest.genMockFromModule('../../../DeviceRegistry');
+    mockDeviceRegistry = new DeviceRegistry();
+    mockDeviceRegistry.isDeviceBusy.mockReturnValue(false);
+
     uut = new FreeDeviceFinder(mockAdb, mockDeviceRegistry);
   });
 
   it('should return the only device when it matches, is online and not busy', async () => {
-    expect(await uut.findFreeDevice(emulator5556.adbName)).toEqual(emulator5556.adbName);
+    mockAdbDevices([emulator5556]);
+
+    const result = await uut.findFreeDevice(emulator5556.adbName);
+    expect(result).toEqual(emulator5556.adbName);
   });
 
   it('should return null when there are no devices', async () => {
     mockAdbDevices([]);
-    expect(await uut.findFreeDevice(emulator5556.adbName)).toEqual(null);
+
+    const result = await uut.findFreeDevice(emulator5556.adbName);
+    expect(result).toEqual(null);
   });
 
   it('should return null when device is busy', async () => {
+    mockAdbDevices([emulator5556]);
     mockAllDevicesBusy();
+
     expect(await uut.findFreeDevice(emulator5556.adbName)).toEqual(null);
   });