Validating Static Resources in a Web Application - c#

Like most web applications mine has static resources that must be part of the deployment or the user receives a 404 response from the server. My thought was to use unit testing to validate two things 1. the resource exists, and 2. the content was not modified. I have tried the following code but it expects (I think) that the files exist in the unit test project.
Solution structure:
WebApplicationProject
- ...
- public
- file.*
- otherfile.*
- web.config
WebApplicationProject.Tests
- AssetTests.cs
Am I going about this all wrong, should this not be part of a unit test and some other gait on the CI build process (Azure DevOps), or am I missing something super obvious? I'm likely asking the wrong questions and going about this the wrong way, because I know I'm not the first person to want to do something like this.
I've read other posts around testing with files, but they all are using test files to drive data for input in some method that consumes the file, or some process that generates a file for comparison. I don't want to do either of these things.
I have also played with the settings making the file an embedded resource, and to always deploy with the project, but the unit test project still cannot access the file the way I'm going about this.
[TestClass]
public class AssetTests
{
[TestMethod]
[DeploymentItem(#".\files\file.*")]
public void AwardLetters()
{
string path = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "file.*");// gets the working path for the testing dll, no files exist here.
Assert.IsTrue(File.Exists("file.*"), "FAIL: file {0} not found", "file.*");// nothing I have tried has access to the projects static resources
}
}
All results end in a file not found exception so far.
I did try to load the reference manually using:
Assembly a = Assembly.LoadFrom("WebApplicationProject");// also used WebApplicationProject.dll
This fails to find the reference. Yes, the reference property copy local is set to true.
I am open to all suggestions, but if you suggest that I have two copies of the files, please fully explain why this is desirable.

Alright here's my MVP. I'll leave this open for a while though in hopes someone has a better solution, it cant be this difficult to access the resources like this, I feel like there should be a way to access the applications directory without having to embed the file in the assembly just to pass it to a test method.
[TestMethod]
public void FileExists()
{
Assembly a = Assembly.LoadFrom(#"..\..\..\WebApplicationProject\bin\WebApplicationProject.dll");
string t = string.Join("", a.GetManifestResourceNames());
Assert.IsTrue(t.Contains("file.*"));
}
Now that I have the file I can also create a test to test the content of the file to validate it's contents.
I still think this is duck tape and it's not elegant at all. So please share your answers and critiques of my solution.

Related

Get application settings without file access

I have an app.config which is working fine.
But I also have a tool for automated testing, that runs some tests in environment without any file access. So I have to read a config file from string (or memory stream), but without mapping it physically because there is no access to file system from this automatic testing process.
In real life, of course, config file is stored somewhere, but for automated testing purposes I need some workaround to read a config file from string stored in memory. Is it even possible? I googled a lot, but the only thing I found is Save it as temp file and then read, but it's not my case.
Avoid a direct dependency from your class on app.config or any other file. Your class doesn't need app.config or Properties.Settings. It needs the values contained in the those files..
If you create a workaround for testing purposes then you're testing a different version of your class. That's the inherent problem - direct dependency on these files isn't testable. It doesn't mean that they're bad in some way or that we shouldn't use them, only that the class that requires the values should not read them from the file.
An ideal solution is constructor injection, a form of dependency injection. Provide the value to the class in its constructor and store it as a field. That way when the class is created it always has the values it needs.
At runtime you can use a dependency injection container - here's a walkthrough on setting one up for WCF. You're likely in a different project type, but the concepts still apply.
But for testing, it's as easy as creating a class and passing whatever value you want to use into the constructor. It's impossible to test with different values when the class reads from settings but it's easy using constructor injection.
Without the configuration file you'll have the default settings. You may override the default values:
Properties.Settings.Default["PropertyName"] = NewPropertyValue";
(Set the correct access modifier on your Settings class and use the correct namespace if it is in a library)
As first option I would go for Settings file in your case.
Even your user won't be ablle to access settings file content. Then it will return a default value for a particualr property.
You can try creaty a silly console app
static void Main(string[] args)
{
Console.WriteLine(Settings.Default.MyProperty);
Console.ReadLine();
}
were you set the your value for MyProperty on the Settings Tab of you Project Properties.
Then you can build your solution, open the binaries folder and delete your exe.config file and you will see that the app will be use default values.
As second option you can use command line arguments. could also be an option for you. (Here the article about some tricky case for command line arguments Backslash and quote in command line arguments )
Third option could be placing your file at c:\users\your app user \AppData\Roaming\YourAppName folder. here you should be granted for file access even for restricted user
For education reason I would also reccomend to look at this article: https://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&l=EN-US&k=k(ApplicationSettingsOverview);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5)&rd=true
Maybe you find the "Load Web Settings" option nice.
Another palce for investigation could be

Debug mysteriously disappearing files from file server

We've been having an issue in our production system lately where some files have been disappearing, or their names changed which breaks the reference to them in the database.
I've added logs to places where I suspect the bug might be, but the codebase is large and there are many flows where files are deleted/copied/moved, so it's difficult to find the problem like this.
My question is whether it's possible to somehow extend the File Delete/CopyTo/MoveTo methods from .NET in order to catch every file operation, and log it, along with the StackTrace to know where the request came from?
If not, then I'd appreciate some tips on how to tackle this issue.
Thank you.
This one is going to be pretty hard for you to capture. You have FileSystemWatcher that you could use to at least capture and bring the file back if it is that critical. But it doesn't give you any context information on how/what made the change, just that a change happened.
The same even goes for shell hooks, which you wouldn't want to do either. (https://msdn.microsoft.com/en-us/library/windows/desktop/cc144063(v=vs.85).aspx)
As for hooking into all calls to the File classes, if would be possible if you had a wrapping class, but otherwise it is going to be hard.
Try Ionad.Fody. It allows to replace static method calls on whole assembly.
PM> Install-Package Ionad.Fody
But it replaces methods completely, so you can't use original functionality by default.
To extend the methods you have to create wrappers in separate project / assembly.
For example. Add new project to your solution. Do NOT apply Fody.Ionad to it. Add wrappers for all of the methods you need to extend:
public static class FileHelper
{
public static void Delete(string path)
{
Console.WriteLine(path);
File.Delete(path);
}
}
Apply Fody.Ionad to your main project where you need to replace methods and add substitutes with StaticReplacement attribute:
[StaticReplacement(typeof(File))]
public static class FileSubstitute
{
public static void Delete(string path)
{
FileHelper.Delete(path);
}
}
Now when you call File.Delete("path") it will output the path to console and delete the file.
If you using some framework that hides File calls in own methods, you have to apply Fody.Ionad add substitutes to that framework too.

Spring.net - Routing of Assembly:// line in config file - Need a base directory

So, I inherited a project that seems to be using Spring.net extensively to do dependency injection. Every executable module implements a method where it goes to the Application config file for the given module and pulls a value along the lines of assembly://Config/Company.Protocol.Config/DIConfig.xml with the middle bit of "Company.Protocol.Config" being slightly different for different executables, bearing a name similar, but not identical to, the project. That XML file seems to be contained within a Config directory at the base of the solution holding the project. While I feel that things are a bit overcomplicated, I see what they're aiming for, storing references to various processing routines in that DIConfig.xml file so that they can be injected.
The problem I'm running into is that I can't actually seem to navigate to those XML files with the specified path above, and my attempts to understand Spring.net don't seem to go far enough to understand where they plan to go with the "Assembly://" bit. I get a Could not resolve resource location error. I've tried contacting the person who last worked with the code, but apparently they inherited it too, and have avoided messing with it for fear of breaking it.
I think that the intent of the line above is to go to the base of the assembly, then the Config directory or project, then get the DIConfig.xml there, but when I try to use that, it can't find the file. I've tried removing the bit between Config and DIConfig.xml just in case this was a matter of there used to have been directories in between, but no dice. I can get it to "work" by dropping the DIConfig.xml file into the same location as the executable and changing the file to be read to simply "DIConfig.xml", thus in the same directory, but of course, that's not very extensible, particularly when I try to run the service that uses this.
The assembly:// tells Spring.net to use the "assembly" protocol to locate the resource. It has the general format assembly://<AssemblyName>/<NameSpace>/<ResourceName>. So in the case of assembly://Config/Company.Protocol.Config/DIConfig.xml there should be an assembly named Config (not necessarily the same as the project name; check the properties). The xml file is contained in the source project that holds the source for the assembly.
Folders in the project can add to the namespace; so if the root namespace of the project is Company.Protocol, then you'll find your xml file in a folder named Config.
The xml file should be marked as an embedded resource. See section 5.2.2.1 of the docs for more.

Assembly.GetEntryAssembly().CodeBase precludes unit testing [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
.NET NUnit test - Assembly.GetEntryAssembly() is null
I'm writing a logging library. I want the library, by default, to write to a directory in the common application data folder named for the application. For example, if the application is called "MyApplication.exe", I want the data saved in "C:\ProgramData\MyApplication".
I'm using this code to construct the path:
private static string loggingDataPath =
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) +
Path.DirectorySeparatorChar +
Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase) +
Path.DirectorySeparatorChar;
This works exactly as expected, with one problem. I can't unit test the library!
When I try to run the unit tests they all fail with a System.NullReferenceException. If I replace the "Assembly.GetEntryAssembly().CodeBase" call with a string the unit tests once again function properly.
I think I understand why this happens but I have no idea how to work around the problem. I hope someone will be able set me on the path of righteousness.
TIA!
UPDATE (5-24-12): I am not trying to unit test the contents of "loggingDataPath". The mere presence of the "Assembly.GetEntryAssembly().CodeBase" call causes ALL unit tests to fail with the above exception. Note that "loggingDataPath" is static (as it must be as this is a static library).
It's not only unit testing that will cause problems.
Given that GetEntryAssembly() can return null when a managed assembly has been loaded from an unmanaged application and also that CodeBase can contain a URL for assemblies downloaded from the Internet, and is not set for assemblies loaded from the GAC, I would avoid attempting this approach for a general-purpose logging library.
If that's not enough to convince you, other problems are (a) non-privileged users won't have write access to CommonApplicationData, and (b) multiple instances of your application attempting to write to the same log file will be a problem.
Instead, I would define the location of the log file in configuration.
Where would you suggest I put it to avoid this problem?
As I said, I would define it in configuration (e.g. an appSetting in app.config). This is the most flexible. If you want to put it under CommonApplicationData, you can use an environment variable that you expand using the Environment.ExpandEnvironmentVariables method when reading from the configuration file. For example:
<appSettings>
<add key="logFile" value="%ALLUSERSPROFILE%\MyApp\MyLogFile.log" />
...
</appSettings>
You still have to solve the problem of giving access to non-privileged users, and avoiding contention when accessing from multiple instances. You say your underlying logging library supports concurrent access, but be aware that this will have a potential performance cost, depending on how verbose your logging is.
Ignoring the sage advice of previous answers and addressing only my question, here is how I fixed the problem:
public static string LoggingDataPath {
get {
return loggingDataPath.Length > 0 ? loggingDataPath :
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) +
Path.DirectorySeparatorChar +
Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase) +
Path.DirectorySeparatorChar;
}
set { loggingDataPath = value; }
}
This solution avoids initializing 'loggingDataPath' on the first access to the static class, thus avoiding the call to 'Assembly.GetEntryAssembly().CodeBase'.

Skip unit test if csv file not found

I have a number of unit tests which rely on the presence of a csv file. They will throw an exception if this file doesn't exist obviously.
Are there any Gallio/MbUnit methods which can conditionally skip a test from running? I'm running Gallio 3.1 and using the CsvData attribute
[Test]
[Timeout(1800)]
[CsvData(FilePath = TestDataFolderPath + "TestData.csv", HasHeader = true)]
public static void CalculateShortfallSingleLifeTest()
{
.
.
.
Thanks
According to the answer in this question, you'll need to make a new TestDecoratorAttribute that calls Assert.Inconclusive if the file is missing.
Assert.Inconclusive is very appropriate for your situation because you aren't saying that the test passed or failed; you're just saying that it couldn't be executed in the current state.
What you have here is not a unit test. A unit test tests a single unit of code (it may be large though), and does not depend on external environmental factors, like files or network connections.
Since you are depending on a file here, what you have is an integration test. You're testing whether your code safely integrates with something outside of the control of the code, in this case, the file system.
If this is indeed an integration test, you should change the test so that you're testing the thing that you actually want tested.
If you're still considering this as a unit test, for instance you're attempting to test CSV parsing, then I would refactor the code so that you can mock/stub/fake out the actual reading of the CSV file contents. This way you can more easily provide test data to the CSV parser, and not depend on any external files.
For instance, have you considered that:
An AntiVirus package might not give you immediate access to the file
A typical programmer tool, like TortoiseSvn, integrates shell overlays into Explorer that sometimes hold on to files for too long and doesn't always give access to a file to a program (you deleted the file, and try to overwrite it with a new one? sure, just let me get through the deletion first, but there is a program holding on to the file so it might take a while...)
The file might not actually be there (why is that?)
You might not have read-access to the path
You might have the wrong file contents (leftover from an earlier debugging session?)
Once you start involving external systems like file systems, network connections, etc. there's so many things that can go wrong that what you have is basically a brittle test.
My advice: Figure out what you're trying to test (file system? CSV parser?), and remove dependencies that are conflicting with that goal.
An easy way would be to include an if condition right at the start of the test that would just execute any code in the test if the CSV file can be found.
Of course this has the big drawback that tests would be green although they haven't actually run and asserted anything.
I agree with Grzenio though, if you have unit tests that rely heavily on external conditions, they're not really helping you. In this scenario you will never really know whether the unit test ran successfully or was just skipped, which contradicts what unit tests are actually for.
In my personal opinion, I would just write the test so that they correctly fail when the file is not there. If they fail this is an indicator that the file in question should be available on the machine where the unit tests run. This might need some manual adjustments at times (getting the file to the computer or server in question), but at least you have reliable unit tests.
In Gallio/MbUnit v3.2 the abstract ContentAttribute and its concrete derived types (such as [CsvData] have a new optional parameter that allows to change the default outcome of a test in case of an error occured while opening or reading the file data source (ref. issue 681). The syntax is the following:
[Test]
[CsvData(..., OutcomeOnFileError = OutcomeOnFileError.Inconclusive)]
public void MyTestMethod()
{
// ...
}

Categories