I have a WPF C# application.
I need it to be able to save 'Products'. These products will have a Product name, Customer name, and firmware location. This is my current code for saving and loading however it is not working. I'm thinking of trying a different approach to it all together:
public class Product
{
private string productName;
private string customerName;
private string firmwareLocation;
public string getProductName()
{
return productName;
}
public bool setProductName(string inputProductName)
{
productName = inputProductName;
return true;
}
public string getCustomerName()
{
return customerName;
}
public bool setCustomerName(string inputCustomerName)
{
customerName = inputCustomerName;
return true;
}
public string getFirmwareLocation()
{
return firmwareLocation;
}
public bool setFirmwareLocation(string inputFirmwareLocation)
{
inputFirmwareLocation = firmwareLocation;
return true;
}
public Product(string inProductName, string inCustomerName, string inFirmwareLocation)
{
inProductName = productName;
inCustomerName = customerName;
inFirmwareLocation = firmwareLocation;
}
public void Save(TextWriter textOut)
{
textOut.WriteLineAsync(productName);
textOut.WriteLineAsync(customerName);
textOut.WriteLineAsync(firmwareLocation);
}
public bool Save(string filename)
{
TextWriter textOut = null;
try
{
textOut = new StreamWriter(filename);
Save(textOut);
}
catch
{
return false;
}
finally
{
if (textOut != null)
{
textOut.Close();
}
}
return true;
}
public static Product Load (string filename)
{
Product result = null;
System.IO.TextReader textIn = null;
try
{
textIn = new System.IO.StreamReader(filename);
string productNameText = textIn.ReadLine();
string customerNameText = textIn.ReadLine();
string firmwareLocationText = textIn.ReadLine();
result = new Product(productNameText, customerNameText, firmwareLocationText);
}
catch
{
return null;
}
finally
{
if (textIn != null) textIn.Close();
}
return result;
}
}
}
It's a little unclear what you mean by "not working" but I'd suggest that you just use the standard .NET serialization/deserialization libraries for this rather than trying to reinvent the wheel. There's no need to do anything custom here. See the following: https://msdn.microsoft.com/en-us/library/mt656716.aspx
As a side note, why are you using getX() and setX() methods instead of properties? It's not standard C#. For example, the following:
private string productName;
public string getProductName()
{
return productName;
}
public bool setProductName(string inputProductName)
{
productName = inputProductName;
return true;
}
should be
public string ProductName
{
get;
set;
}
I'm guessing that one of the reasons your code isn't working is that it has multiple glaring race conditions. For example, all 3 of your writes are asynchronous and fired off right after the other; there's no guarantee that the previous one will be done when you start the next one. It's not even clear to me that you're guaranteed to write the lines in a particular order (which you're counting to be the case in your deserialization logic). It's also completely possible (likely, actually) that you'll close the file in the middle of your write operations.
I'd also suggest a "using" block for the file streams.
Related
I am creating an Automation Framework using Selenium C#, currently I am working on the object repository part. So I would like to know what all types of files I can use as the Object Repository.Currently I am thinking of using either XML or Excel but I am not sure which one is better performance wise, so can any of you share your views on this and also let me know if there are any other options.
I am planning to use XmlDocument for reading xml and oledb connection for reading excel.
By Object repository i think you mean different elements, their locators and some other required attributes, because As far as i know selenium do not have concept of Object Repository inherently.
If so, you need to think about who is going to maintain this Repository,
with few thousand locators, rather than performance maintainability would be a major issue.
Also, think about making it isolated by implementing an interface, so in future if you decide to change implementation because of any issue, it will be not impact your framework.
And XML, Excel, text file(with any delimiter), a Database, json file are good contenders for this.
Selenium does not work with XML pages by default, as browsers do not show XML files as XML, but show its as converted to html files.
For the task I had used following code (its based on HTMLAgilityPack):
XmlActions.cs
namespace BotAgent.Ifrit.Core.Xml
{
using HtmlAgilityPack;
public partial class XmlActions
{
private HtmlDocument _xmlDoc;
private HtmlNode _rootNode;
public XmlActions()
{
_xmlDoc = new HtmlDocument();
}
private void Update()
{
string pageSource = Brwsr.CurrPage.PageSource.Replace("\r\n", string.Empty);
_xmlDoc.LoadHtml(pageSource);
_rootNode = _xmlDoc.DocumentNode;
}
public NodeSingle Elmnt(string xpath)
{
Update();
var currNode = _rootNode.SelectSingleNode(xpath);
return new NodeSingle(currNode);
}
public NodesMultiple Elmnts(string xpath)
{
Update();
var nodesGroup = _rootNode.SelectNodes(xpath);
return new NodesMultiple(nodesGroup);
}
}
}
XmlActions.NodeSingle.cs
using System;
namespace BotAgent.Ifrit.Core.Xml
{
using HtmlAgilityPack;
partial class XmlActions
{
public class NodeSingle
{
private readonly HtmlNode _currNode;
public string Text
{
get
{
return CleanUpStringFromXml(_currNode.InnerText);
}
}
public string TagName
{
get
{
return _currNode.OriginalName;
}
}
public string XmlInner
{
get
{
return _currNode.InnerHtml;
}
}
public string XmlOuter
{
get
{
return _currNode.OuterHtml;
}
}
public NodeSingle(HtmlNode currentNode)
{
_currNode = currentNode;
}
public bool Exist()
{
if (_currNode == null)
{
return false;
}
return true;
}
public bool AttributesExist()
{
return _currNode.HasAttributes;
}
public bool AttributeExist(string attributeName)
{
if (_currNode.Attributes[attributeName] != null)
{
return true;
}
return false;
}
public string AttributeValue(string attrName)
{
return _currNode.GetAttributeValue(attrName, string.Empty);
}
public bool HaveChildren()
{
var firstChildNode = _currNode.FirstChild;
if (firstChildNode != null)
{
return true;
}
return false;
}
public NodeSingle FirstChild()
{
HtmlNode node = null;
try
{
node = _currNode.ChildNodes[1];
}
catch (Exception)
{
//// No need to throw exception, its normal if there are no child
}
return new NodeSingle(node);
}
public NodeSingle Parent()
{
return new NodeSingle(_currNode.ParentNode);
}
private string CleanUpStringFromXml(string xml)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(xml);
var root = doc.DocumentNode;
root.RemoveAllChildren();
return root.OuterHtml.Replace(" ", string.Empty);
}
}
}
}
XmlActions.NodesMultiple
namespace BotAgent.Ifrit.Core.Xml
{
using System.Collections.Generic;
using System.Linq;
using HtmlAgilityPack;
partial class XmlActions
{
public class NodesMultiple
{
private readonly HtmlNodeCollection _nodesGroup;
public int Count
{
get
{
return _nodesGroup.Count;
}
}
public NodesMultiple(HtmlNodeCollection nodesGroup)
{
this._nodesGroup = nodesGroup;
}
public NodeSingle GetElementByIndex(int index)
{
var singleNode = _nodesGroup.ElementAt(index);
return new NodeSingle(singleNode);
}
public List<NodeSingle> GetAll()
{
return _nodesGroup.Select(node => new NodeSingle(node)).ToList();
}
}
}
}
I had used my own framework code here, but this must not create problem for you to change it to clear selenium code.
After this you can create static XML var with browser instance and use like this:
bool isIdExist = Brwsr.Xml.Elem(".//div[1]").AttributeExist("id");
or
bool haveChild = Brwsr.Xml.Elem(".//div[1]").FirstChild().Exist;
Looking for design guidelines for the following problem.
I'm receiving two string values - action and message and have to call appropriate method which processes string message (processM1MessageVer1, processM1MessageVer2, processM2MessageVer1...). The method I have to call depends on the given string action. There are 2 versions (but in future there might be more) of each processing method. The version of method I have to call is determined by global variable version. Every method returns object of different type (ResultObject1, ResultObject2...). The result has to be serialized, converted to base64 and returned back.
Is there more elegant way of writing this (eliminate duplicate code, make possible future changes easier, reduce code...):
string usingVersion = "ver1";
public string processRequest(string action, string message)
if (usingVersion == "ver1"){
processRequestVer1(action, message);
}
else{
processRequestVer2(action, message);
}
}
//version 1
public string processRequestVer1(string action, string message){
string result = "";
switch (action){
case "m1":
ResultObject1 ro = processM1MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
case "m2":
ResultObject2 ro = processM2MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
case "m3":
ResultObject3 ro = processM3MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
}
return result;
}
//version 2
public string processRequestVer2(string action, string message){
string result = "";
switch (action){
case "m1":
ResultObject1 ro = processM1MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
case "m2":
ResultObject2 ro = processM2MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
case "m3":
ResultObject3 ro = processM3MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
}
return result;
}
It would be simplier if messages that have to be processed are of different object types instead of strings so that appropriate method could be called polymorphically. The fact that every process method returns different object type also complicates things even more. But these don't depend on me and I cannot change it.
My approach (make it more object oriented, and you should justify whether it's appropriate to create class structure depending on how complex your processing logic is. If your processing logic is only little then maybe this is over-engineering):
For serialize and convert to base 64, I assume you have some logic to do those tasks in a generic way. If not, move those to sub class also
public interface IRequestProcessorFactory
{
IRequestProcessor GetProcessor(string action);
}
public class FactoryVersion1 : IRequestProcessorFactory
{
public IRequestProcessor GetProcessor(string action)
{
switch(action)
{
case "m1":
return new M1Ver1RequestProcessor();
case "m2":
return new M2Ver1RequestProcessor();
case "m3":
return new M3Ver1RequestProcessor();
default:
throw new NotSupportedException();
}
}
}
public class FactoryVersion2 : IRequestProcessorFactory
{
public IRequestProcessor GetProcessor(string action)
{
switch(action)
{
case "m1":
return new M1Ver2RequestProcessor();
case "m2":
return new M2Ver2RequestProcessor();
case "m3":
return new M3Ver2RequestProcessor();
default:
throw new NotSupportedException();
}
}
}
public interface IRequestProcessor
{
string ProcessRequest(string message);
}
public class RequestProcessorBase<T>
{
public string ProcessRequest(string message)
{
T result = Process(message);
string serializedResult = Serialize(result);
return ConvertToB64(serializedResult);
}
protected abstract T Process(string message);
private string Serialize(T result)
{
//Serialize
}
private string ConvertToB64(string serializedResult)
{
//Convert
}
}
public class M1Ver1RequestProcessor : RequestProcessorBase<ResultObject1>
{
protected ResultObject1 Process(string message)
{
//processing
}
}
public class M2Ver1RequestProcessor : RequestProcessorBase<ResultObject2>
{
protected ResultObject2 Process(string message)
{
//processing
}
}
public class M3Ver1RequestProcessor : RequestProcessorBase<ResultObject3>
{
protected ResultObject3 Process(string message)
{
//processing
}
}
public class M1Ver2RequestProcessor : RequestProcessorBase<ResultObject1>
{
protected ResultObject1 Process(string message)
{
//processing
}
}
public class M2Ver2RequestProcessor : RequestProcessorBase<ResultObject2>
{
protected ResultObject2 Process(string message)
{
//processing
}
}
public class M3Ver2RequestProcessor : RequestProcessorBase<ResultObject3>
{
protected ResultObject3 Process(string message)
{
//processing
}
}
Usage:
string action = "...";
string message = "...";
IRequestProcessorFactory factory = new FactoryVersion1();
IRequestProcessor processor = factory.GetProcessor(action);
string result = processor.ProcessRequest(message);
The switch is still there in factory class, but it only returns processor and doesn't do actual work so it's fine for me
First - define interface that suit you best, like this
public interface IProcessMessage
{
string ActionVersion { get; }
string AlgorithmVersion { get; }
string ProcessMessage(string message);
}
Then create as many implementation as you need
public class processorM1Ver1 : IProcessMessage
{
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM1MessageVer1(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
public string ActionVersion {get { return "m1"; }}
public string AlgorithmVersion {get { return "ver1"; }}
}
public class processorM2Ver1 : IProcessMessage
{
public string ActionVersion {get { return "m2"; }}
public string AlgorithmVersion {get { return "ver1"; }}
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM2MessageVer1(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
}
public class processorM1Ver2 : IProcessMessage
{
public string ActionVersion {get { return "m1"; }}
public string AlgorithmVersion {get { return "ver2"; }}
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM1MessageVer2(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
}
Now you need something that know which implementation is best in current context
public class MessageProcessorFactory
{
private MessageProcessorFactory() { }
private static readonly MessageProcessorFactory _instance = new MessageProcessorFactory();
public static MessageProcessorFactory Instance { get { return _instance; }}
private IEnumerable<IProcessMessage> _processorCollection;
IEnumerable<IProcessMessage> ProcessorCollection
{
get
{
if (_processorCollection == null)
{
//use reflection to find all imlementation of IProcessMessage
//or initialize it manualy
_processorCollection = new List<IProcessMessage>()
{
new processorM1Ver1(),
new processorM2Ver1(),
new processorM1Ver2()
};
}
return _processorCollection;
}
}
internal IProcessMessage GetProcessor(string action)
{
var algorithVersion = ReadAlgorithVersion();
var processor = ProcessorCollection.FirstOrDefault(x => x.AlgorithmVersion == algorithVersion && x.ActionVersion == action);
return processor;
}
private string ReadAlgorithVersion()
{
//read from config file
//or from database
//or where this info it is kept
return "ver1";
}
}
It can be use in such way
public class Client
{
public string ProcessRequest(string action, string message)
{
IProcessMessage processor = MessageProcessorFactory.Instance.GetProcessor(action);
return processor.ProcessMessage(message);
}
}
I have a class like this
#region Properties
private static string inputURL;
public static string InputURL
{
get { return inputURL; }
set { inputURL = value; }
}
private static string outputURL;
private static string ffBaseURL = "format=xml&";
public static string FFBaseURL
{
get { return ffBaseURL; }
set { ffBaseURL = value; }
}
private static string excludeParam = "fullurl,log";
public static string ExcludeParam
{
get { return excludeParam; }
set { excludeParam = value; }
}
private static string currentCategoryID = "234";
public static string CurrentCategoryID
{
get { return currentCategoryID; }
set { currentCategoryID = value; }
}
private static string navigationParameters = "query=*&log=navigation&filterCategoryId=" + currentCategoryID;
public static string NavigationParameters
{
get { return navigationParameters; }
set { navigationParameters = value; }
}
#endregion
#region Methods
public static string NavigationCall()
{
List<string> excludeParams = new List<string>(excludeParam.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));
foreach (string key in HttpContext.Current.Request.QueryString.Keys)
{
if (!excludeParams.Contains(key))
{
FFBaseURL += key + "=" + HttpContext.Current.Request[key] + "&";
}
}
FFBaseURL += NavigationParameters;
if (Common.IsInternalIP())
{
FFBaseURL += "&log=internal";
}
outputURL = ffBaseURL;
return outputURL;
}
#endregion
As you can see I have a static function called NavigationCall() ,it is mandatory that this function remains static.And when I calls this function from my website the function returns wrong values in each function call because of the static properties i declared.We all know static properties will retain their values after the exection of the programe.
So lets say when i call these function first time I gets a result "tesresult1",second time when i reloads my webpage it gives me a result "testresult1testresult1".I think you got the problem now.
I Have tried to solve this issue by declaring static variable values again ,but it does not looks like a good way to programe things.
I tried to make the properties non static .but it returns error as NavigationCall() is a static function i can't call non static properties inside it.
Now I am searching for a correct way to resolve this issue, I think this problem came to me because of the wrong understanding of OOPS concept.Can any one lend a hand here to solve the case or if the issue is vast point to some resources where i can understand how to find a solution?
Instead of using static properties, you can pass all the parameters to your static method.
public static string NavigationCall(
string inputURL,
string ffBaseURL,
string excludeParam,
string currentCategoryID,
string navigationParameters
)
{
// the body of your method
}
You can also bundled all properties into Custom object and pass it to method. Also you have to make NavigationCall thread safe for any solution. Are static methods thread safe ?
public static string NavigationCall(CustomNavigation objCustomNavigation)
//Custom object.
public class CustomNavigation
{
public string InputURL {get;set;}
public string FBaseURL{get;set;}
public string ExcludeParam{get;set;}
public string CurrentCategoryID {get;set;}
public string NavigationParameters{get;set;}
}
I'd suggest to introduce a parameter object (as #mit suggested) and use the opportunity to encapsulate some of your logic there. This should instantly simplify your method. Maybe you could then make some of these properties private, because they'll only be needed in logic encapsulated in the parameter object.
//Your static method
public static string NavigationCall(CustomNavigation customNavigation)
//Disclaimer: I have no idea, whether this is an appropriate name.
//It really depends on what you want to do with his class
class CustomNavigation
{
public string InputURL { get; private set; }
public string FFBaseURL { get; private set; }
public IEnumerable<string> ExcludeParams { get; private set; }
public string CurrentCategoryID { get; private set; }
public string NavigationParameters { get; private set; }
public CustomNavigation(string inputUrl, string excludeParam, string fBaseUrl, string currentCategoryID, string navigationParameters)
{
// various guard clauses here...
NavigationParameters = navigationParameters;
CurrentCategoryID = currentCategoryID;
FFBaseURL = fBaseUrl;
InputURL = inputUrl;
// Parse string here -> Makes your method simpler
ExcludeParams = new List<string>(excludeParam.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));
}
//Example for encapsulating logic in param object
public void AddKeys(HttpContext currentContext)
{
var keys = currentContext.Request.QueryString.Keys
.Cast<string>()
.Where(key => !ExcludeParams.Contains(key));
foreach (var key in keys)
FFBaseURL += key + "=" + currentContext.Request[key] + "&";
}
}
I am trying to modify an object after its creation. I would like to set the properties of this object to -1 for int or string.empty "" for a string. Bellow is a sample code of what I already have.
class TestClassAccess{
public int MyPropInt { get; set { ModifyOnAccessDenied<int>(value); } }
public string MyPropString { get; set { ModifyOnAccessDenied<string>(value); } }
public TestClassAccess() { }
private T ModifyOnAccessDenied<T>(T propertyToChange) {
var _hasAccess = false; //not really important how this is made
if (!_hasAccess)
{
if (propertyToChange is string)
propertyToChange = string.Empty;
else if (propertyToChange is int)
propertyToChange = -1;
}
return propertyToChange;
}
}
so.. issues i am having.
It doesn't compile as I cannot convert property to change to string or int.
I don't knot if i can use set methods like this.
Is this possible or am i being to ambitious.
Thank.s
KJ
If you are checking for specific types in a generic function you are probably doing something wrong. In this case you can easily just pass in a default value rather than having it hard coded:
private T ModifyOnAccessDenied<T>(T newValue, T defaultValue) {
var _hasAccess = false; //not really important how this is made
if (!_hasAccess)
{
newValue = defaultValue;
}
return newValue;
}
I've also renamed propertyToChange to newValue because what you have in this function is the new value, not a property.
Also your property definitions will not work. If you need to include any logic in your getter or setting you cannot use the auto-initializer syntax and must implement the property with a backing field.
There doesn't seem to be a point in making this function generic if it needs specific action for each type. This seems more appropriate.
class TestClassAccess
{
public int MyPropInt { get; set { ModifyOnAccessDenied<int>(value); } }
public string MyPropString { get; set { ModifyOnAccessDenied<string>(value); } }
public TestClassAccess() { }
private static volatile bool _hasAccess = false;
private string ModifyOnAccessDenied<string>(string propertyToChange)
{
if (!_hasAccess)
return string.Empty;
return propertyToChange;
}
private int ModifyOnAccessDenied<int>(int propertyToChange)
{
if (!_hasAccess)
return -1;
return propertyToChange;
}
}
You can however do this using dynamics, but this does require .NET 4.0
private T ModifyOnAccessDenied<T>(T propertyToChange)
{
if (!_hasAccess)
{
if (propertyToChange is string)
return (dynamic)string.Empty;
else if (propertyToChange is int)
return (dynamic)(int)-1;
}
return propertyToChange;
}
Fully working sample:
static class Program
{
[STAThread]
static void Main()
{
TestClassAccess test = new TestClassAccess();
test.MyPropInt = 4;
test.MyPropString = "TEST";
Console.WriteLine("MyPropInt {0}, MyPropString '{1}'",test.MyPropInt, test.MyPropString);
// Prints "MyPropInt -1, MyPropString ''
}
class TestClassAccess
{
private int myPropInt = 0;
public int MyPropInt { get { return myPropInt; } set { myPropInt = ModifyOnAccessDenied<int>(value); } }
private string myPropString = string.Empty;
public string MyPropString { get { return myPropString; } set { myPropString = ModifyOnAccessDenied<string>(value); } }
public static volatile bool _hasAccess = false;
private T ModifyOnAccessDenied<T>(T propertyToChange)
{
if (!_hasAccess)
{
if (propertyToChange is string)
return (dynamic)string.Empty;
else if (propertyToChange is int)
return (dynamic)(int)-1;
}
return propertyToChange;
}
}
}
Can I use ApprovalTests with PDF's? I tried using the FileLauncher but it seems the identical PDF's are slightly different at file (bit) level. Or did I use it wrongly?
[TestMethod]
[UseReporter(typeof(FileLauncherReporter))]
public void TestPdf()
{
var createSomePdf = PdfCreate();
ApprovalTests.Approvals.Verify(new FileInfo(createSomePdf.FileName));
}
The Pdf is most likely being created with a timestamp. Depending on the method used to create the pdf, you might be able to mock out the created time. but I had to scrub it.
Here's the code I used to do that.
public static void VerifyPdf(string coverFile)
{
ScrubPdf(coverFile);
Approvals.Verify(new ExistingFileWriter(coverFile));
}
private static void ScrubPdf(string coverFile)
{
long location;
using (var pdf = File.OpenRead(coverFile))
{
location = Find("/CreationDate (", pdf);
}
using (var pdf = File.OpenWrite(coverFile))
{
pdf.Seek(location, SeekOrigin.Begin);
var original = "/CreationDate (D:20110426104115-07'00')";
var desired = new System.Text.ASCIIEncoding().GetBytes(original);
pdf.Write(desired, 0, desired.Length);
pdf.Flush();
}
}
I found a command-line tool, diff-pdf. Compares 2 PDFs and returns exit code 0 if they're the same, 1 if they differ. Download + extract + add it to your PATH.
Downside - it must render both PDFs to perform the diff. If they're big, perf hit.
Approver (based heavily on ApprovalTests.Approvers.FileApprover):
public class DiffPdfApprover : IApprovalApprover
{
public static void Verify(byte[] bytes)
{
var writer = new ApprovalTests.Writers.BinaryWriter(bytes, "pdf");
var namer = ApprovalTests.Approvals.GetDefaultNamer();
var reporter = ApprovalTests.Approvals.GetReporter();
ApprovalTests.Core.Approvals.Verify(new DiffPdfApprover(writer, namer), reporter);
}
private DiffPdfApprover(IApprovalWriter writer, IApprovalNamer namer)
{
this.writer = writer;
this.namer = namer;
}
private readonly IApprovalNamer namer;
private readonly IApprovalWriter writer;
private string approved;
private ApprovalException failure;
private string received;
public virtual bool Approve()
{
string basename = string.Format(#"{0}\{1}", namer.SourcePath, namer.Name);
approved = Path.GetFullPath(writer.GetApprovalFilename(basename));
received = Path.GetFullPath(writer.GetReceivedFilename(basename));
received = writer.WriteReceivedFile(received);
failure = Approve(approved, received);
return failure == null;
}
public static ApprovalException Approve(string approved, string received)
{
if (!File.Exists(approved))
{
return new ApprovalMissingException(received, approved);
}
var process = new Process();
//settings up parameters for the install process
process.StartInfo.FileName = "diff-pdf";
process.StartInfo.Arguments = String.Format("\"{0}\" \"{1}\"", received, approved);
process.Start();
process.WaitForExit();
if (process.ExitCode != 0)
{
return new ApprovalMismatchException(received, approved);
}
return null;
}
public void Fail()
{
throw failure;
}
public void ReportFailure(IApprovalFailureReporter reporter)
{
reporter.Report(approved, received);
}
public void CleanUpAfterSucess(IApprovalFailureReporter reporter)
{
File.Delete(received);
if (reporter is IApprovalReporterWithCleanUp)
{
((IApprovalReporterWithCleanUp)reporter).CleanUp(approved, received);
}
}
}
To Verify:
DiffPdfApprover.Verify(pdfBytes);
diff-pdf can visually show diffs as well. I rolled a Reporter for this, but don't use it much. I think it'll come in handy if there are regressions after initial report dev (which is where I'm at right now).
public class DiffPdfReporter : GenericDiffReporter
{
private static readonly string Path = FindFullPath("diff-pdf.exe");
public DiffPdfReporter() : base(Path,
GetArgs(),
"Please put diff-pdf.exe in your %PATH%. https://github.com/vslavik/diff-pdf. And restart whatever's running the tests. Everything seems to cache the %PATH%.") { }
private static string GetArgs()
{
return "--view \"{0}\" \"{1}\"";
}
private static string FindFullPath(string programInPath)
{
foreach (var path in from path in Environment.GetEnvironmentVariable("path").Split(';')
select path)
{
var fullPath = System.IO.Path.Combine(path, programInPath);
if (File.Exists(fullPath))
return fullPath;
}
return null;
}
}
Looks like this is built in to ApprovalTests now.
usage:
Approvals.VerifyPdfFile(pdfFileLocation);
See the source:
public static void VerifyPdfFile(string pdfFilePath)
{
PdfScrubber.ScrubPdf(pdfFilePath);
Verify(new ExistingFileWriter(pdfFilePath));
}