I am new to C#. I have been trying to write a xUnit Test for the method below ("ExecuteExportCsvCommand()"). However, it has some UI control as you can see below. Therefore, I don't know how to test the function. If anyone can explain, I would be really thankful.
public void ExecuteExportCsvCommand()
{
ExecuteCancelPopOutWindowCommand();
var previousState = View.IsEnabled;
View.IsEnabled = false;
try
{
var exportDialog = new ExportPrintDataDialog();
var result = exportDialog.ShowDialog();
if (result.GetValueOrDefault())
{
var projectData = _projectService.GetProject3dObject(Project.Id);
IEnumerable<object> orderedList;
orderedList = projectData.Select((x, i) => new ExportData {
Index = i,
Time = x.Time,
LayerNumber = x.LayerNumber,
X = x.X,
Y = x.Y,
Z = x.Z,
ArcVoltage = x.ArcVoltage,
Current = x.Current,
WireFeedSpeed = x.WireFeedSpeed,
WireFeedSpeedB = x.WireFeedSpeedB,
RatioWireFeedSpeed = (x.WireFeedSpeed == 0) ? 0 : x.WireFeedSpeedB / x.WireFeedSpeed,
BuildHeight = x.WallHeight,
LeadTemperature = x.LeadTemperature,
TrailTemperature = x.TrailTemperature,
OxygenLevel = x.OxygenLevel,
GasFlowA = x.GasFlow.First(),
GasFlowB = x.GasFlow.Last(),
TempChannels = x.TempChannels //preferably last as array of 8 values
}).ToList();
//Define Column headers
var heightHeader = _options.CorriOptions.Value.UseWallHeight ? "Wall Height" : "Layer Height";
var wfsHeader = Project.ProcessEnum == EnumProjectProcess.CW_MIG
? new []{ "Hot Wire Feed Speed", "Cold Wire Feed Speed" }
: new[] { "Wire Feed Speed 1", "Wire Feed Speed 2" };
var headers = new Dictionary<string, string>()
{
{ "Index" , "Ordinal Number" },
{ "Time" , "Time" },
{ "LayerNumber" , "Layer Number" },
{ "X" , "Pos X" },
{ "Y" , "Pos Y" },
{ "Z" , "Pos Z" },
{ "ArcVoltage" , "Arc Voltage" },
{ "Current" , "Current" },
{ "WireFeedSpeed" , wfsHeader.First() },
{ "WireFeedSpeedB" , wfsHeader.Last() },
{"RatioWireFeedSpeed", "R-Ratio"},
{ "BuildHeight" , heightHeader },
{ "LeadTemperature" , "Lead Temperature" },
{ "TrailTemperature" , "Trail Temperature" },
{ "OxygenLevel" , "Oxygen Level" },
{ "GasFlowA" , "GasFlow Channel A" },
{ "GasFlowB" , "GasFlow Channel B" },
{ "TempChannels" , "End Effector Temperature (Channels 1 - 8)" }
};
var saveFileDialog = new SaveFileDialog
{
Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*",
FilterIndex = 1,
RestoreDirectory = true,
FileName = string.Concat(DateTime.Now.ToString("yyyy-MM-ddTHHmm"), "_log"),
};
if (saveFileDialog.ShowDialog().GetValueOrDefault())
{
using var writer = new StreamWriter(saveFileDialog.FileName);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap(new ExportDataMapper(headers));
csv.WriteRecords(orderedList);
}
}
}
finally
{
View.IsEnabled = previousState;
ExportDataBtnChecked = false;
}
}
The test environment was set up already as you can see below. I just have to write the test for the method mentioned. If you need further details, please let me know. thanks.
public class ProjectPrintViewModelTests
{
private readonly ProjectPrintViewModel _sut;
private readonly IHost _host;
private readonly Mock<IPermissionChecker> _permissionChecker = new Mock<IPermissionChecker>();
private readonly Mock<ILogService> _logService = new Mock<ILogService>();
private readonly Mock<IAuditService> _auditService = new Mock<IAuditService>();
private readonly Mock<IProjectService> _projectService = new Mock<IProjectService>();
public ProjectPrintViewModelTests()
{
var colorList = new List<string> {
"DefaultMinColor",
"DefaultLowColor",
"DefaultNormColor",
"DefaultHighColor",
"DefaultMaxColor"
};
var app = new Application();
colorList.ForEach(x => app.Resources.Add(x, Color.FromRgb(255, 255, 255)));
_host = MockEnvironment.BuildHost();
IoCContainer.SetServiceProvider(_host.Services);
var options = _host.Services.GetRequiredService<OptionsContainer>();
var valueService = _host.Services.GetRequiredService<IValuePointService>();
var project = new ProjectViewModel(options);
_sut = new ProjectPrintViewModel(
options,
project,
_logService.Object,
_permissionChecker.Object,
_auditService.Object,
_projectService.Object,
valueService,
false);
}
/// Write Tests Below Here
[Fact]
public void ExecuteCsvFileCommand_WhenValueAreValid()
{
//Assemble
//Act
_sut.ExecuteExportCsvCommand();
//Assert
CodePudding user response:
This is one of the many disadvantages of having poor design and non-clean architecture.. Separating UI code from business logic helps you in testing your business logic well.
Keep in mind that unit tests should run/return as fast as possible. Apparently, showing a SaveFileDialog will stop the test until a user selects the path, this is wrong!
You should pull all UI-related code out to a separate component that delivers UI services, For example:
public interface IUiServices
{
void CancelPopOutWindow();
void SaveViewState();
void RestoreViewState();
bool ShowExportDialog();
string ShowSaveFileDialog();
}
The most important part is, when testing, you'd mock the UIServices to not do any real UI work (Ex. opinging SaveFileDialog, it will just return a valid test path to save files in there).. So ExecuteExportCsvCommand
can be look like this..
public void ExecuteExportCsvCommand()
{
_uiServices.CancelPopOutWindow();
_uiServices.SaveViewState();
try
{
var b = _uiServices.ShowExportDialog();
if (b)
{
var projectData = _projectService.GetProject3dObject(Project.Id);
IEnumerable<object> orderedList;
orderedList = projectData.Select((x, i) => new ExportData {
Index = i,
Time = x.Time,
LayerNumber = x.LayerNumber,
X = x.X,
Y = x.Y,
Z = x.Z,
ArcVoltage = x.ArcVoltage,
Current = x.Current,
WireFeedSpeed = x.WireFeedSpeed,
WireFeedSpeedB = x.WireFeedSpeedB,
RatioWireFeedSpeed = (x.WireFeedSpeed == 0) ? 0 : x.WireFeedSpeedB / x.WireFeedSpeed,
BuildHeight = x.WallHeight,
LeadTemperature = x.LeadTemperature,
TrailTemperature = x.TrailTemperature,
OxygenLevel = x.OxygenLevel,
GasFlowA = x.GasFlow.First(),
GasFlowB = x.GasFlow.Last(),
TempChannels = x.TempChannels //preferably last as array of 8 values
}).ToList();
//Define Column headers
var heightHeader = _options.CorriOptions.Value.UseWallHeight ? "Wall Height" : "Layer Height";
var wfsHeader = Project.ProcessEnum == EnumProjectProcess.CW_MIG
? new []{ "Hot Wire Feed Speed", "Cold Wire Feed Speed" }
: new[] { "Wire Feed Speed 1", "Wire Feed Speed 2" };
var headers = new Dictionary<string, string>()
{
{ "Index" , "Ordinal Number" },
{ "Time" , "Time" },
{ "LayerNumber" , "Layer Number" },
{ "X" , "Pos X" },
{ "Y" , "Pos Y" },
{ "Z" , "Pos Z" },
{ "ArcVoltage" , "Arc Voltage" },
{ "Current" , "Current" },
{ "WireFeedSpeed" , wfsHeader.First() },
{ "WireFeedSpeedB" , wfsHeader.Last() },
{"RatioWireFeedSpeed", "R-Ratio"},
{ "BuildHeight" , heightHeader },
{ "LeadTemperature" , "Lead Temperature" },
{ "TrailTemperature" , "Trail Temperature" },
{ "OxygenLevel" , "Oxygen Level" },
{ "GasFlowA" , "GasFlow Channel A" },
{ "GasFlowB" , "GasFlow Channel B" },
{ "TempChannels" , "End Effector Temperature (Channels 1 - 8)" }
};
string path = _uiServices.ShowSaveFileDialog();
if (!string.IsNullOrEmpty(path))
{
using var writer = new StreamWriter(path);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap(new ExportDataMapper(headers));
csv.WriteRecords(orderedList);
}
}
}
finally
{
_uiServices.RestoreViewState();
ExportDataBtnChecked = false;
}
}