views:

103

answers:

7

I want that my application catches the exceptions and e-mail them when running in production, but when I am running my MSTest unit tests I want it to throw the exceptions, so I can debug. My current code always e-mails the exceptions. There is any way to know if the current process was invoked by the unit tests?

One way I can think is to test the Enviroment.CurrentDirectory and similar variables. There is a better one?

+1  A: 

You could use compilation directives (like #IF DEBUG) to execute some code piece when testing and another code set when compiled in released mode;

Another approach can be write different TraceListeners, so you can log your exceptions in plain text or send your email just by setting up in your .config file.

Rubens Farias
Massa, valeu pela dica
Jader Dias
@Jader: é curintia
Rubens Farias
This approach unnecessarily clutters the code. The best practice is to mock the email sender.You can read more at http://en.wikipedia.org/wiki/Mock_object
Vitaliy
+3  A: 

You can also set a flag on your App.Config for the e-mail routine to verify if it should send those e-mails, and in your Test Suite set that flag to false.

Lucas
Yeah, but my test suit app.config is a soft link to my application app.config. I would have to duplicate it =) Not a problem.
Jader Dias
Só dá Brasil nessa thread
Jader Dias
A: 

Well, basically what you want to do is to Mock out the mail sending during testing. I assume you have a class that is responsible for sending emails and your code looks something like:

..
..
catch(Exception e)
{
   EmailSender.Send(e);
}

A good unit test should be isolated, meaning that the executing code should not cross class boundaries, not to mention sending messages to other processes!

I suggest you read up material on Mocking and Mocking frameworks on the net. I personally use the RhinoMock framework.

Vitaliy
+1  A: 

You could do something REALLY EVIL like use log4net and us the IsDebug flag . this is of course madness.

A better way would be to Inject whatever sends the email into your class and when you run unit tests pass in a mock object for this (moq) this way the emails will not be sent when code is running under test conditions.

There are several good frameworks for doing this

Moq Rhino Mocks

these are my two favourites.

When I talk about injection I mean something like this

public class Foo
{
    private Emailer _emailer;

    public Foo(Emailer mailer)
    {
        _emailer = mailer;
    }

    public void SomeMethod()
    {
         ...
         try
         {
             ...
         }
         catch(SomeException ex)
         {
             _emailer.SendEmail(ex);
         }
         finally
         {}
    }
}

The frameworks essentially allow you under certain conditions to pass in an object which is basically fake, you can then speicify behaviour of these objects and use assertions etc in nunit.

But to answer you question directly you COULD use a flag in your config file, but this is not as neat or as useful.

krystan honour
+2  A: 

This is a classic use case for dependency injection or a service locator. Instead of hardwiring your application to send emails, have your application get a notification service from the service locator, and have it call that. In production, configure the service locator to return an email sending service; in the test environment, configure it to return a service that does nothing, or add the notification to a list, or whatever.

You don't need to go for the full whack dependency injection here: a very simple service locator will suffice. Similarly, the injection of the test notification service can be done through test fixture code. Here's a really simple example:

public static class ServiceLocator
{
  private static INotificationService _notificationService = new EmailNotificationService();

  public static INotificationService NotificationService
  {
    get { return _notificationService; }
  }

  // For test use only
  public static void SetNotificationService(INotificationService notificationService)
  {
    _notificationService = notificationService;
  }
}

(A real service locator would provide a more flexible interface, along the lines of IServiceContainer/IServiceProvider, so that you could mock out multiple different services; this is just to illustrate the idea.)

Then in your test code:

[SetUp]
public void Setup()
{
  ServiceLocator.NotificationService = new DiscardingService();
}

(Using NUnit terminology here for the method that gets run before each test -- not sure what the MSTest equivalent is.)

An advantage of this is that you can now test that the right notifications are being sent: have your test notification service capture the notifications in a list, and you can make assertions about that list to verify that notifications are being sent and are correct.

Again, note that this is not a full explanation of DI or service locators, and my sample code is by no means the best way to do it.

itowlson
Service locators are indeed a good solution.
krystan honour
krystan: I don't like them much as they hide dependencies and pollute app code with "get this service, set this service" calls. Constructor-based DI means the app won't even build if the dependencies are not satisfied. With DI, It's easy to look at any piece of code in isolation and reason about its dependencies.
Mark Simpson
Mark: fair point. The reason I didn't discuss DI in detail is that it requires a great deal more explanation than the simplistic service locator approach, and constructor-based DI would probably require considerably more refactoring of the original poster's application than a service locator would. But there's certainly a case that proper DI would likely be more maintainable as the scenario got more complex.
itowlson
I started out with service locators, then DI with constructor injection seemed to be a natural progression, so that's a fair point :)
Mark Simpson
A: 

It would help if you described more about your specific scenario.

It doesn't sound like you're running unit tests if they're sending emails. The units of code shouldn't be controlling, or indeed have any knowledge of concrete error reporting types unless they're easily configurable at an app level.

It sounds like what you're trying to do is hack around something that is not set up properly. If the problem is non-critical and it's definitely safe to continue, then you should probably just use a logger and then respond to the error appropriately. There's probably a log4net appender that sends emails if you still need that functionality (and if you don't configure log4net, it won't do anything, hence your tests won't send any emails).

This doesn't impact testability, as you can feed in the error condition and then check whether the appropriate response was taken (e.g. "if I cannot read a config file, then return the defaults").

If you're detecting fatal errors, then you should look at creating an exception handler at the top level of your app (i.e. immediately after it starts). You can do this by wiring up a handler for the AppDomain.UnhandledException event amongst other things. That way, if an exception is unhandled, you will still be able to catch it properly, send the email via your exception handler and then terminate the application in a graceful fashion.

You would only install the exception handler in at the start of a normal application run. Save for the initial bootsrapping/wiring phase, your app has no knowledge of it. Your units of code would be unaware, and you could test like usual.

Mark Simpson
A: 

A quick way could be...

public static class Tester
{
    public static bool InUnitTest
    {
        get
        {
            return Environment.StackTrace.IndexOf("Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestRunner") > 0;
        }
    }
}

Then in your application you can just

catch (Exception e)
{
    if (!Tester.InUnitTest)
    {
       ... // send mail
    }
}

You could also implement that within any Emailsender class you may be using so you dont need to worry about changing every catch block

Badger
I would not litter my code with something like this. Use Configuration files for defining if to send email or not.
Fadeproof