I have a client class that opens a connection to a Serial Port. I want to write unit tests against this class in a way that when my client class sends data to the mocked serial port and the test can respond with data so the client class can continue. In other words I want to test basically a request/response over a serial connection without an actual devices.
SignClient.ts
export class SignClient {
private readonly DEFAULT_BAUD_RATE = 9600;
private readonly TIMEOUT = 5000;
serial?: SerialPortStream;
comPort: string;
baudRate: number;
private binding?: BindingInterface;
private timeout?: NodeJS.Timeout;
parser?: SignClientResponseParser;
constructor(comPort: string, baudRate?: number, binding?: BindingInterface) {
this.comPort = comPort;
this.baudRate = baudRate || this.DEFAULT_BAUD_RATE;
this.binding = binding;
}
async connect(): Promise<SignClient> {
return new Promise<SignClient>((resolve, reject) => {
this.serial = new SerialPortStream({
binding: this.binding || SerialPort.binding,
path: this.comPort,
baudRate: this.baudRate
}, (err) => {
if (err !== null) {
this.serial = undefined;
reject(err);
return;
}
this.parser = this.serial?.pipe(new SignClientResponseParser());
resolve(this);
return;
});
});
}
async send<T extends Response>(packet: TransmissionPacket): Promise<T> {
return new Promise<T>((resolve, reject) => {
if (this.serial === undefined) {
throw new Error("Serial port is not open");
}
if (packet.expectsResponse) {
const responseListener = this.parser?.on('data', async (data) => {
responseListener?.removeAllListeners();
clearTimeout(this.timeout as NodeJS.Timeout);
this.timeout = undefined;
try {
const response = await ResponseFactory.parse(data);
resolve(response as T);
}
catch (err) {
reject(err)
}
});
const errorListener = this.parser?.on('error', (err) => {
errorListener?.removeAllListeners();
reject(err);
});
}
this.serial.write(packet.toBuffer(), (err) => {
if (err !== undefined) { reject(err); return; }
if (packet.expectsResponse) {
this.timeout = setTimeout(() => {
this.serial?.removeAllListeners('data');
reject("Timeout");
return;
}, this.TIMEOUT);
return;
}
resolve(undefined as unknown as T);
return;
});
});
}
isOpen(): boolean {
return this.serial?.isOpen || false;
}
}
I have hacked a version of a test using a time out here:
beforeEach(() => {
SerialPortMock.binding.createPort('/dev/serial1', { echo: false, record: false })
});
afterEach(() => {
SerialPortMock.binding.reset();
});
test('should send and listen for a malformed packet', async () => {
const signClient = await new SignClient('/dev/serial1', undefined, MockBinding).connect();
setTimeout(() => {
signClient.parser?.emit('data', Buffer.from(malformedPacket));
}, 100);
await expect(signClient.send(new TestTransmissionPacket(true))).rejects.toThrowError();
});
Is there a way to hook into the mocked serial port so when my SignClient class writes data I can respond on that serial port with test data?