Replace implementation of instantiated class witho

2019-02-18 02:04发布

I have legacy code I don't want to touch.

public class LegacyCode{
    public LegacyCode() {
        Service s = new ClassA();
        s.getMessage();
    }
}

Where ClassA provides a CORBA service call.

public class ClassA implements Service{
    @Override
    public String getMessage() {
       // Make CORBA service call...
       return "Class A";
    }
}

And the interface Service looks like;

public interface Service {
    String getMessage();
}

For test purposes, I want to replace the implementation of Service (in LegacyCode implemented by ClassA) with a stub.

public class ClassB implements Service {
    @Override
    public String getMessage() {
        return "Stub Class B";
    }
}

So far so good. But is it possible without any modifications at the shown legacy code to load ClassB instead of ClassA at the instantiation of ClassA?

// In my test workbench
new LegacyCode(); // "Stub Class B"

I've tried to write a custom classloader and load it at application start by java vm arguments but only the first class (here LegacyCode) was loaded by that loader.

Thanks in advance

3条回答
Viruses.
2楼-- · 2019-02-18 02:51

Using PowerMock you can create a mock (or stub) for a constructor code. The answer is taken from this link. I'll try to convert it to match exactly to your use case:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassA.class)
public class LegacyTester {
    @Test
    public void testService() {
         // Inject your stub
         PowerMock.createMock(ClassA.class);
         Service stub = new MyServiceStub();
         PowerMock.expectNew(ClassA.class).andReturn(stub);
         PowerMock.replay(stub, ClassA.class);

         // Implement test logic here

         LegacyCode legacyCode = new LegacyCode();

         // Implement Test steps

        // Call verify if you want to make sure the ClassA constructor was called
        PowerMock.verify(stub, ClassA.class)
    }
}

This way you inject your stub when the call to ClassA constructor happens, without changing the legacy code. Hope that's what you needed.

查看更多
来,给爷笑一个
3楼-- · 2019-02-18 02:51

The easier thing to do is to just create a ClassA in your test code. Declare it in the same package originally found in the actual code and use the same method names. Just make the method do nothing.

For example, if your project structure looks like this:

+- src
  +- main
    +- java
      +- com.company.code
        -- LegacyCode.java
        -- Service.java
      +- com.company.code.service
        -- ClassA.java
  +- test
    +- java
      +- com.company.code
        -- LegacyCodeTest.java

Create an alternative ClassA under /src/test/java/com/company/code/:

package com.company.code.service;
/** Replaces the original implementation for testing purposes */
public class ClassA implements Service{
    @Override
    public String getMessage() {
       return "I replaced the original implementation";
    }
}

Now your project structure will look like this:

+- src
  +- main
    +- java
      +- com.company.code
        -- LegacyCode.java
        -- Service.java
      +- com.company.code.service
        -- ClassA.java
  +- test
    +- java
      +- com.company.code
        -- LegacyCodeTest.java
      +- com.company.code.service
        -- ClassA.java

When your unit tests execute, the ClassA from your test folder will be loaded. This is way simpler than using a mock framework especially if your old code is messy, not dependency-injection-friendly and the actual ClassA is used everywhere.

Just keep in mind that this sort of strategy can make your test code a bit more obscure, and you can't test the actual implementation of ClassA itself, nor easily provide alternative implementations for different test scenarios.

查看更多
家丑人穷心不美
4楼-- · 2019-02-18 02:52

One way would be with AspectJ. I agree with Hovercraft, this is a good case for dependency injection but if you can't change the source code, AspectJ might be your tool of choice.

This explains the AspectJ case better than I can: Can AspectJ replace "new X" with "new SubclassOfX" in third-party library code?

查看更多
登录 后发表回答