Reputation: 2356
I am trying to develop some tests for a Windows 10 UWP application which uses Windows.Devices.Bluetooth.BluetoothLEDevice
. I have a plain class that is part of my application which has a private BluetoothLEDevice
type field.
class MyDevice
{
private Windows.Devices.Bluetooth.BluetoothLEDevice BluetoothLEDevice;
public string SomeProperty { get; set; }
public MyDevice(Windows.Devices.Bluetooth.BluetoothLEDevice bluetoothLEDevice)
{
BluetoothLEDevice = bluetoothLEDevice;
var characteristic = BluetoothLEDevice.GetGattService(...)
.GetCharacteristics(...)
.First();
characteristic.ValueChanged += OnValueChanged;
}
// TODO: Write tests for this method
private OnValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
string message = Encoding.ASCII.GetString(args.CharacteristicValue.ToArray());
// Parse `message`
SomeProperty = parsed;
}
...
}
The methods of said class use the events and methods of bluetoothLEDevice
, some are private and others are public. How can I test the public methods of MyDevice
?
I have tried something like this which I think could work, but I can see that it will take hundreds of lines of code and quite a few extra classes because I would need to implement a lot of stuff in FakeBluetoothLEDevice
in orde for it to work properly.
I changed MyDevice
to accept a wrapper instead and then create two implementations of the wrapper. One for testing, and the other for real use.
class MyDevice
{
private MyApp.IBluetoothLEDeviceWrapper bluetoothLEDevice;
}
Then in my test I use the fake.
private void ValueChangedEventDataParsingTest()
{
var device = new FakeBluetoothLEDevice();
var myDevice = new MyDevice(device);
device.InvokeValueChanged("this is the value for a fake ValueChangedEvent");
Assert.Equals(probe.SomeProperty, "expected");
}
Are there any frameworks (available for UWP) that would help me achieve what I want? Or even a better approach that would save me some pain?
Upvotes: 1
Views: 426
Reputation: 247153
In stead of focusing on implementation concerns focus on what functionality you want your abstraction to expose. Using your simplified example I was able to replicate it with some refactors to only interact with the desired functionality.
[TestClass]
public class DeviceTests {
[TestMethod]
public void _ValueChangedEventDataParsingTest() {
//Arrange
var message = "message";
var expected = "expected";
var device = new FakeBluetoothLEDevice(message, expected);
var sut = new MyDevice(device);
//Act
device.InvokeValueChanged(message);
//Assert
Assert.AreEqual(expected, sut.SomeProperty);
}
public interface IBlueToothService {
Action<string> ValueChangedHandler { get; set; }
}
public class FakeBluetoothLEDevice : IBlueToothService {
private string message;
private string parsed;
public FakeBluetoothLEDevice(string message, string expected) {
this.message = message;
this.parsed = expected;
}
public Action<string> ValueChangedHandler { get; set; }
public void InvokeValueChanged(string p) {
var handler = ValueChangedHandler ?? delegate { };
if (p == message) {
ValueChangedHandler(parsed);
}
}
}
public class MyDevice {
private IBlueToothService device;
public string SomeProperty { get; set; }
public MyDevice(IBlueToothService device) {
this.device = device;
device.ValueChangedHandler = handler;
}
private void handler(string parsedValue) {
SomeProperty = parsedValue;
}
}
}
Use separation of concerns and move the heavy lifting of implementation concerns behind the actual implementations. It vastly simplifies the consumers of such functionality.
If the concern is to test he parsing functionality then abstract the out into its own concern as well. Don't have classes doing more than they need to (SRP)
private OnValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) {
string message = Encoding.ASCII.GetString(args.CharacteristicValue.ToArray());
// Parse `message`
var parsed = parsingServce.Parse(message);
SomeProperty = parsed;
}
That way the parsing service implementation will only need to be tested for ts core functionality.
But from an abstraction perspective the parser is not needed as a dependency when testing higher level functionality.
I advise reviewing the current design and refactoring it to be more SOLID.
Upvotes: 2