This is a migrated thread and some comments may be shown as answers.

Mocking System.Threading.Timer

4 Answers 591 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Stefano
Top achievements
Rank 1
Stefano asked on 21 Feb 2019, 02:38 PM

Hi guys,

I need to test the handler of a Timer in one class. I'm using Future Mocking to mock the Timer inside it.
Is there some way in order to invoke the callback?

Here my class to test:

 

internal class DatabaseCleaner
{
 
      private Timer mobjTimer = null;
 
      internal void Start()
      {
 
            mobjTimer = new Timer(CleanDB, null, 5000, 1000 * 60 * 10);
      }
 
      internal void Stop()
      {
         if (mobjTimer != null)
         {
            mobjTimer.Dispose();
            mobjTimer = null;
         }
      }
 
      private void CleanDB(object state)
      {

       // do stuff to test

        }
}


Thanks a lot.

Stefano

4 Answers, 1 is accepted

Sort by
0
Mihail
Telerik team
answered on 22 Feb 2019, 03:54 PM
Hi Stefano,

Yes, you can invoke the private method of an internal class. As you haven't mentioned, I will assume that the internal class DatabaseCleaner is defined in a different assembly than where the test is defined. If this is the case, then what you should do is obtain an instance of that internal class.

Here is an example of how this could be done:
var typeInfo = Assembly.Load("ClassLibrary").DefinedTypes.Where((t) => t.FullName == "ClassLibrary.DatabaseCleaner").FirstOrDefault();
var type = typeInfo.AsType();
var instance = Activator.CreateInstance(type);

After that, you should create a PrivateAccessor with an argument the created instance and call the required method. Here is an example:
object state = 10;
PrivateAccessor privateAccessor = new PrivateAccessor(instance);
privateAccessor.CallMethod("CleanDB", state);

If the test scenario requires it you could arrange the call to CleanDB to executed just once or the appropriate amount. Here an example of how a full test for this method could look like:
[TestMethod]
public void TestMethod()
{
    // Arrange
    var typeInfo = Assembly.Load("ClassLibrary").DefinedTypes.Where((t) => t.FullName == "ClassLibrary.DatabaseCleaner").FirstOrDefault();
    var type = typeInfo.AsType();
    var instance = Activator.CreateInstance(type);
 
 
    object state = 10;
    Mock.NonPublic.Arrange(type, "CleanDB", state).CallOriginal().OccursOnce();
 
    // Act
    Mock.NonPublic.MakePrivateAccessor(instance).CallMethod("CleanDB", state);
 
    // Assert
    Mock.Assert(instance);
}

I hope that this information answers your question.

Regards,
Mihail
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Stefano
Top achievements
Rank 1
answered on 25 Feb 2019, 01:41 PM

Hi Mihail,

 

Thanks for the reply.

The assembly of the test is the same of my class. Sorry, I forgot to tell you.

I saw your solution, but is there come way to mock the timer? Because in your way I'm not testing the timer feature.. but I'm just testing a private method.

Thanks,

Stefano

0
Mihail
Telerik team
answered on 25 Feb 2019, 04:06 PM
Hi Stefano,

It seems I didn't understand your question correctly. Please excuse me.

You should use future mocking for the timer. However, I am afraid that your exact scenario with the private callback CleanDB is not supported. I have logged a new feature request of your behalf. Here is a link if you would like to follow the item and receive status updates: Future mocking of public class with non-public arguments

I am not sure if this is an option for you but I will share it anyway. If you modify the access modifier of the callback CleanDB to "internal" then the test case will be possible. The reason behind this is that the future mocking depends on expressions and in order for the expression to be compiled successfully all elements of that expression should be visible to the test method.

Here is how the test method will look like in this case:
[TestMethod]
public void TestMethod()
{
    bool isProcessed = false;
 
    // Arrange
    var dbCleaner = Mock.Create<DatabaseCleaner>(Behavior.CallOriginal);
    Mock.Arrange(() => new Timer(dbCleaner.CleanDB, null, 5000, 1000 * 60 * 10))
        .DoInstead(() => new Timer((o)=> isProcessed = true, null, 0, 0));
 
    // Act
    dbCleaner.Start();
    Thread.Sleep(1000);
 
    // Assert
    Assert.AreEqual(true, isProcessed);
}

Please have in mind that in this test I am asserting that the callback is called by the timer. I am also triggering the execution of the callback faster than the original code. I am doing this to speed up the test and it is based on the assumption that the Timer will work as expected regardless of the exact arguments for triggering the callback.

If you would like to assert that the CleanDB is executed then you should find a way to verify that depending on the job done inside that method. Here is how the arrangement should look like in this case:
Mock.Arrange(() => new Timer(dbCleaner.CleanDB, null, 5000, 1000 * 60 * 10))
    .DoInstead(() => new Timer(dbCleaner.CleanDB, null, 0, 0));

I hope this helps.

Regards,
Mihail
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Ivo
Telerik team
answered on 25 Jun 2019, 08:26 AM
Hello Stefano,

I hope you are doing well. I would like to let you know about the available options we have found which can solve the issue using the current JustMock API level.

The first option verifies the exact call to the private will be made. In this case you can create delegate and bind the instance method DatabaseCleaner.CleanDB to it. Than the result delegate can be used as an argument matcher in the arrangement. Here is the code:

[TestMethod]
public void TestMethod2()
{
    // Arrange
    var sut = Mock.Create<DatabaseCleaner>(Behavior.CallOriginal);
    var dbCleanerTimerCallback = (TimerCallback)Delegate.CreateDelegate(typeof(TimerCallback), sut, "CleanDB");
 
    Mock.Arrange(() => new Timer(dbCleanerTimerCallback, null, 5000, 1000 * 60 * 10))
        .IgnoreInstance()
        .MustBeCalled();
 
    // Act
    var dbCleaner = new DatabaseCleaner();
    dbCleaner.Start();
 
    // Assert
    Mock.Assert(sut);
}
The second (more generic) option verifies that the delegate of a given type will be called. As an opposite for the previous option, here the argument matcher looks for the specified type, but not for the particular instance. The test looks like as following:

[TestMethod]
public void TestMethod3()
{
    // Arrange
    var sut = Mock.Create<DatabaseCleaner>(Behavior.CallOriginal);
 
    Mock.Arrange(() => new Timer(Arg.IsAny<TimerCallback>(), Arg.AnyObject, Arg.AnyInt, Arg.AnyInt))
        .IgnoreInstance()
        .MustBeCalled();
 
    // Act
    var dbCleaner = new DatabaseCleaner();
    dbCleaner.Start();
 
    // Assert
    Mock.Assert(sut);
}
 
It would be nice to share whether these solutions are good enough for you, so we can plan further activities on the related feature request.


Regards,
Ivo
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Tags
General Discussions
Asked by
Stefano
Top achievements
Rank 1
Answers by
Mihail
Telerik team
Stefano
Top achievements
Rank 1
Ivo
Telerik team
Share this question
or