DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Unit-Testing Multi-Threaded Code Timers

Unit-Testing Multi-Threaded Code Timers

Dror Helper user avatar by
Dror Helper
·
Mar. 31, 13 · Interview
Like (0)
Save
Tweet
Share
8.06K Views

Join the DZone community and get the full member experience.

Join For Free

Writing unit tests for multi-threaded is not simple and could even be impossible for some scenarios – how could you test that an asynchronous method was not called?

Since most unit testing examples tend to be rather trivial I’ve decided to try and explain other more complex scenarios – preferably without using any calculator examples.

The “Timer” problem

Consider the Timer class (or rather classes), .NET has several classes called Timer, In this post I’m referring to System.Threading.Timer and System.Timers.Timer which is built on top of the former. 
Both perform an action in another thread (using the Thread Pool) in intervals (not accurately – but I won’t go there in this post).

Consider the following class:

public class ClassUnderTest
{
    private readonly Timer _timer;
 
    public ClassUnderTest()
    {
        _timer = new Timer(1000);
        _timer.Elapsed += PerformPeriodicAction;
        _timer.Start();
    }
 
    public ClassUnderTest(Timer timer)
    {
        _timer = timer;
        _timer.Elapsed += PerformPeriodicAction;
        _timer.Start();
    }
 
    private void PerformPeriodicAction(object sender, ElapsedEventArgs e)
    {
        // Perform very important task here!
    }
}
Testing code that employs timers can be tricky. Unfortunately there’s a tendency to write a test that looks like this when timers are involved:
[Test]
public void TestJustWrong()
{
    var cut = new ClassUnderTest();
 
    // Make sure the timer executed at least once
    Thread.Sleep(5000);
 
    // Check that something happens
}

So what is the problem?

Although it might seem like a valid unit test – in fact it’s a test that would fail from time to time - whenever the timer would happen to “tick” for more than the sleep period.

These kind of tests are known as the “the test that tends to fail” and usually “fixed” by running the build script another time.

You do not want a test that you cannot trust to fail only when a bug is introduced into your system.
Sadly I’ve seen this code – a lot! But no more - writing a good unit test for that class is simple, in fact there is more than one alternative that creates a simple, trustworthy unit test.

Solution 1 – invoke the handler instead

Instead of waiting for the time to execute why not call the method that handles the timer’s event?
First we need to make sure that the method we’re going to call is internal or public. I personally don’t like changing my production code that much in order to make the code “testable” but it’s a way.
Now all we have to do is call the method thus making sure that execution happens exactly when we want it – and a unit test is born:

[Test]
public void TestByMethod()
{
    var cut = new ClassUnderTestForTestability();
 
    // Let's call the method
    cut.PerformPeriodicAction(this, null);
 
    // Check that something happens
}

Solution 2 – invoke the timer at your leisure

Most modern Isolation (a.k.a Mocking) frameworks have the ability to invoke events as well as create fake objects.

We’re going to create a fake timer and invoke the elapsed event. Using simple constructor injection we’ll pass the timer to our class under test prior to invocation and we have a more robust unit test:

Test]
public void TestWithFakeTimer()
{
    var fakeTimer = Isolate.Fake.Instance<Timer>();
 
    var cut = new ClassUnderTest(fakeTimer);
 
    // Let's invoke the event
    Isolate.Invoke.Event(() => fakeTimer.Elapsed += null, this, null);
 
    // Check that something happens
}

I’m using Typemock Isolator but you can use the isolation framework of your choice The syntax is strange but once you’ll get used to it, it’s simple.

The added value is that we’re causing the real flow of our program to execute – in a synchronized and timely manner.


Conclusion

Although there are many ways to test code that uses timers I find myself employing the two above most of the time.

I plan to write a few more posts on the other pitfall of testing multi-threaded code (and how to avoid them) but until then – happy coding.

unit test

Published at DZone with permission of Dror Helper, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Using AI and Machine Learning To Create Software
  • Writing a Modern HTTP(S) Tunnel in Rust
  • GPT-3 Playground: The AI That Can Write for You
  • Secrets Management

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: