1

I'm using Mockito in a Java project with Spring and Struts and I'm having problems with testing actions.

I'm not using the Struts2 jUnit plugin to save time with the tests using this approach: Strut 2.3.1.2 Unit test, how to remove Spring codependency vs NPE with getContext().

The problem is when in my action, when getText() is called I've got a NullPointerException.

I'm trying to spy this method, that's inherited from ActionSupport but I don't find a way because the action is annotated with InjectMocks in the test.

Here's a simplied example of the classes:

Parent:

public class ActionSupport {
    public String getText(String  aTextName){
        return this.getTextProvider().getText(aTextName);
    }
}

My action:

public class MyAction extends ActionSupport {
    @Autowired private IMyService myService;

    public String execute(){
        getText("SomeText");
        myService.something();
        return SUCCESS;
    }
}

Test:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
    @Mock private IMyService myService;
    @InjectMocks private MyAction myAction;

    @Before
    public void before() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() {
        myAction.execute();
    }
}

I'm getting this exception:

java.lang.NullPointerException
    at com.opensymphony.xwork2.util.TextParseUtil.translateVariables(TextParseUtil.java:167)
    at com.opensymphony.xwork2.util.TextParseUtil.translateVariables(TextParseUtil.java:126)
    at com.opensymphony.xwork2.util.TextParseUtil.translateVariables(TextParseUtil.java:48)
    at com.opensymphony.xwork2.util.LocalizedTextUtil.getDefaultMessage(LocalizedTextUtil.java:663)
    at com.opensymphony.xwork2.util.LocalizedTextUtil.findText(LocalizedTextUtil.java:534)
    at com.opensymphony.xwork2.util.LocalizedTextUtil.findText(LocalizedTextUtil.java:362)
    at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:208)
    at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:123)
    at com.opensymphony.xwork2.ActionSupport.getText(ActionSupport.java:103)
    at es.MyAction.execute(MyAction.java:148)

And if I annotate MyAction with @Spy maintaining @InjectMocks I've got an StackOverflowError.

How can I spy only ActionSupport.getText() and let mockito inject mocks on my action?

Community
  • 1
  • 1
Borja
  • 35
  • 4
  • 1
    I see a lot of questions like this when using Mock libraries. My general feeling is: if this is that painful to wrap all such stuff in mock objects, and assuming your system is reasonably well designed, why not just use Dummy classes implementing interfaces, to avoid all such pointless pain? – Alex Suo Jan 22 '15 at 10:59
  • I don't know how to make tests that way. In fact I don't know what you're really talking about :) – Borja Jan 22 '15 at 11:07
  • I.e. Ideally your modules should communicate via interfaces than concrete implementation classes; then you just write up a dummy class implementing your interface, and return the expected value you want. This way you write up some more code but you have perfect control and transparency to what those dummy classes are doing. – Alex Suo Jan 22 '15 at 11:25
  • Ah ok, I've think I've get it now. But I can't communicate via interfaces with the actions because that's not how _Struts2_ works. With the services it's what we're actually doing. – Borja Jan 22 '15 at 11:42

1 Answers1

3

I would avoid using @InjectMocks as it fails silently.

Just add a constructor to your MyAction still using @Autowired i.e. constructor injection. This also helps guarantee required dependencies.

You dont need both initMocks and MockitoJUnitRunner.

public class MyAction extends ActionSupport {

    private IMyService myService;

    @Autowired
    public MyAction(MyService myService) {
        this.myService = myService;
    }

    public String execute(){
        getText("SomeText");
        myService.something();
        return SUCCESS;
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

    @Mock
    private IMyService myService;

    private MyAction myAction;

    @Before
    public void setup() {
        myAction = spy(new MyAction(myService));
    }

    @Test
    public void test() {

        assertThat(myAction.execute(), equalTo(Action.SUCCESS));

        verify(myAction, times(1)).getText();

        verify(myService, times(1)).something();
    }
}

InjectMocks fails silently

Constructor injection discussion

clD
  • 2,523
  • 2
  • 22
  • 38