5

I'm writing unit tests for someone else's code that I'm not allowed to modify.

Say I have:

class BadClass
{
    public BadClass()
    {
        // the service isn't going to be running during testing
        // it also has no interface
        someFlag = AGloballyStoredService.getSomeFlag();
    }

    public bool someFlag;

}

that is used by:

class AnotherBadClass
{
    public AnotherBadClass(BadClass badClass)
    {
        someFlag = badClass.someFlag;
    }
    public bool someFlag;
}

Say I want the test:

public void AnotherBadClassConstructorTest()
{
    BadClass badClass = new BadClass();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClass);
    Assert.IsNotNull(anotherBadClass);
}

I would like to mock BadClass, but it has no interface and it fails during its constructor if the service isn't running.

Considering that I can't modify the code that I'm testing, is there a straightforward way to make this work?

If it comes down to it, I can tell them that they have to either let me modify the existing code base or accept sub 10% coverage for this module.

Damien
  • 263
  • 1
  • 4
  • 15
  • 2
    You will likely need to use a specialized library like [Microsoft Fakes](https://msdn.microsoft.com/en-us/library/hh549175.aspx) to intercept and re-write the methods to redirect them to your mocks. – Scott Chamberlain Sep 06 '16 at 19:52
  • 1
    I'm afraid only integration tests are really an option here, if you can't touch the code. – Red Sep 06 '16 at 19:55

3 Answers3

9

Considering that I can't modify the code that I'm testing, is there a straightforward way to make this work?

No. However, I see two, non-straightforward options:

A) Use Microsoft Fakes to provide the mocks by rewriting the library. You won't have to modify the code. Example: you can modify static DateTime.Now to return the date of your choice like this:

System.Fakes.ShimDateTime.NowGet = 
() =>
{ return new DateTime(fixedYear, 1, 1); };

B) You can try to inherit the tested class and override used methods/ctors. There are two limitations though. You have to be able to instantiate/swap the instance of tested class with your mock object (by using reflection you can get to private members) and the mocked method must be virtual. In your case you would create:

class BadClassMock : BadClass
{
    public BadClassMock()
    {
    }

    public bool someFlag;

    public void InitForTest()
    {
        someFlag = true;
    }
}

And it will work, as long as you won't call the base constructor. You can use nasty FormatterServices.GetUninitializedObject() trick from here: Creating instance of type without default constructor in C# using reflection

and later:

public void AnotherBadClassConstructorTest()
{
    BadClassMock badClassMock = (BadClassMock)FormatterServices.GetUninitializedObject(typeof(BadClassMock));
    badClassMock.InitForTest();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClassMock);
    Assert.IsNotNull(anotherBadClass);
}

Yes, it is pretty big workaround. Obviously the best way is to convince the author to modify the code.

Community
  • 1
  • 1
piotrwest
  • 2,098
  • 23
  • 35
  • That could work, but it's looking like I might not be able to bring in Fakes. Do know if there's any similar functionality in Moq? – Damien Sep 07 '16 at 13:09
  • Moq works on different level. Moq allows you to create mocks using inheritance, virtual methods or interfaces - it doesn't work on the compiled code, thus it won't allow you to mock BadClass. With Fakes you would just swap the compiled code of BadClass to whatever you want. You can still try the B option. – piotrwest Sep 07 '16 at 20:17
0

Actually you can mock BadClass without modifying the code, by using TypeMock Isolator you will be able to mock a class even if it's not virtual or an interface. for example here is the test you tried to create:

[TestMethod]
public void CreateAnotherBadClass_IsNotNull()
{
    var fakeBadClass = Isolate.Fake.Instance<BadClass>();
    var anotherBadClass = new AnotherBadClass(fakeBadClass);

    Assert.IsNotNull(anotherBadClass);
}

In this test i created a fake instance of BadClass and sent it as a parameter to AnotherBadClass c'tor.

JamesR
  • 745
  • 4
  • 15
0

You will want to use either TypeMock or JustMock to mock this if you don't want to change the code to take in an interface. Both are paid tools and I have yet to find a free tool that can do what you are wanting to do. I prefer JustMock. The syntax for JustMock would look like so:

public void AnotherBadClassConstructorTest()
{
    BadClassMock badClassMock = Mock.Create<BadClassMock>();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClassMock);
    Assert.IsNotNull(anotherBadClass);
}

If you are going to be doing this a lot or wanting to test legacy code that was not written with unit tests in mind these tools are well worth the cost in my opinion. You can always do the free trial to try them out too.

JCisar
  • 2,584
  • 2
  • 29
  • 28