davidebbo / WebActivator

Apache License 2.0
242 stars 49 forks source link

How to unit/integration test? #6

Closed gdoten closed 11 years ago

gdoten commented 11 years ago

I have a unit test that calls RunPreStartMethods but my pre-start method is never called because ActivationManager.AppCodeAssemblies always returns empty since HostingEnvironment.IsHosted is always false, at least using xUnit. How do I get RunPreStartMethods to find my methods that are decorated with the WebActivator attributes so they can be run from a unit test envrionment?

davidebbo commented 11 years ago

If your code is in App_Code, it gets compiled dynamically at runtime, and you generally won't be able to unit test it, WebActivator or not. But if your code is in a bin assembly that gets compiled by msbuild, then it should work.

See https://github.com/davidebbo/WebActivator/blob/master/WebActivatorTest/WebActivatorUnitTest.cs#L32 for an example that uses that.

gdoten commented 11 years ago

Thanks, that's the exact sample environment I've been trying to get to work. This is a web app in its own assembly, so no App_Code and all that. I even used the WebActivator source directly from my test/web project combination and I just don't see how AppCodeAssemblies will ever return my assemblies since IsHosted always comes back false (I can see it's false because I have a breakpoint there).

davidebbo commented 11 years ago

AppCodeAssemblies won't return your assemblies, but it doesn't need to. The one to look at is ActivationManager.Assemblies. Hopefully you can isolate the difference between your test and mine (which works).

gdoten commented 11 years ago

The only difference I see between your test and mine is that you call ActivationManager.Reset to setup your test. I can't do that because that method is internal. That's probably not a problem because that method looks like it just resets two variables to their default values anyhow. It would be nice to be able to teardown WebActivator as you do though, but then that method would have to be made public I believe.

When I walk through the Assemblies code as it executes it the GetAssemblyFiles only returns WebActivator.dll and none of my assemblies, unlike your test which returns I think three assemblies including the one containing the test codes. Figured out why, sort of. The GetAssemblyFiles directory in your case is "G:\WebActivator\WebActivatorTest\bin\Debug" but in my case it is "D:\temp\gd\temp\b2d9c6d3-78e7-4fa2-950e-cc4fbedbc203\b2d9c6d3-78e7-4fa2-950e-cc4fbedbc203\assembly\dl3\af4e736a\07e4acda_1bdecd01" which most definitely is not where my solution lives. In that %temp% directory there is: AssemblyInfo.ini, WebActivator.DLL, and WebActivator.PDB. System.Enrivonment.CurrentDirectory, at the same time, indicates "G:\blah\src\blah.Tests.Unit\bin\Debug" which is what I would expect GetAssemblyFiles to use.

Any idea what's up with this directory? This is a new one on me! I don't see WebActivator in the GAC or anything.

1.5.2.0. Windows 8 64 bit. .NET 4.5. MVC 4 app. IIS Express.

An aside: What happens if the call to Assembly.LoadFrom in the Assemblies property happens to throw an OutOfMemoryException or any number of other exceptions that the .NET run-time may decide to throw from that method?

davidebbo commented 11 years ago

That folder looks like a .Net Shadow Copy cache location. Maybe something to do with how your test framework is loading assemblies? Who exactly loads WebActivator in your scenarios? What might "gd" stand for?

For the exception, I suppose it should probably just ignore a few known ones instead of all. But some are definitely expected when dealing with native DLLs that have nothing to do with .NET code.

gdoten commented 11 years ago

gd is me. My temp environment variable points to "D:\temp\gd\temp".

Seems like this is something xUnit is doing. I just switched over to VS [TestClass] and [TestMethod] and that works OK!

Probably just catching Win32Exception, FileNotFoundException, BadImageFormatException, and SecurityException would be better than catching (and ignoring) every possible exception. The other couple of exceptions that LoadFrom can throw would maybe be best handled by checking the argument first. What scares me is that something could happen in the handling of the call to that method and/or in the method itself which has nothing to do with "WebActivator" and any traces are now gone. Just my opinion.

gdoten commented 11 years ago

And that's it: xUnit. It is indeed doing a shadow copy of the files. So for anyone else: edit your .xunit project file and change the shadow-copy for the test assembly to "false" and then WebActivator should test as expected. I don't yet know if there's a way to tell the VS-based xUnit test runner to not do the shadow copy, but will ask in there forum.

Thanks, David, for your quick ideas on what to check.

davidebbo commented 11 years ago

Nice! For the exception handling part, I'll take a pull request if you want to make that change :)

gdoten commented 11 years ago

Ah but wait, there's more! I just read the last entry in this thread:

http://xunit.codeplex.com/discussions/67866

The suggestion is to use the CodeBase property rather than the location of the assembly. So I changed the last part of this line in WebActivator:

string directory = HostingEnvironment.IsHosted ? HttpRuntime.BinDirectory : Path.GetDirectoryName(typeof(ActivationManager).Assembly.Location);

to use this instead:

: Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);

And now my test code works fine using xUnit's VS plugin or their GUI. And notice that this line is slightly different than the thread uses; they want the whole file path, WebActivator just wants the directory name.

I can't say I know enough about this to say whether this change has adverse side-effects or not. It makes sense to me, but there might be something I'm missing, and I know WebActivator has a big user base. I'd be happy to make this change to the code if it makes sense (along with the exception catching). Let me know what you think!

davidebbo commented 11 years ago

Sounds good, I think that will work. Note that typeof(ActivationManager).Assembly and Assembly.GetExecutingAssembly() is likely equivalent. The relevant different is the CodeBase/LocalPath part.

This code is only used in Unit Test scenarios anyway, since we always use HttpRuntime.BinDirectory at runtime in the site, so you;re not affecting too many people here. And I think in the case you don't use shadow copying, it'll behave the same as the current code anyway (please verify that).

gdoten commented 11 years ago

I just tested shadowing on and off with the xUnit GUI and it works for both using the new code. It also now works in the VS plugin thingy too, which didn't work before, but I don't know how to specify "/noshadow" in that environment to test it. Now I guess I'll have to get something out into NuGet that has a pre-start chunk of code!