Intercept/Stub a class method in a called function

2,037 views
Skip to first unread message

Russell Robinson

unread,
Jun 26, 2013, 3:38:44 AM6/26/13
to moc...@googlegroups.com
A colleague wrote a unit test where a called method creates an object that performs a network communication.

Not ideal as a unit test, so we need to fix this.

Substituting the network communication, we want to provide a "fake" return value.

Here's the current test logic:
@Test testCreateJob -> calls createJob -> creates Payment object -> calls GetToken -> calls PayPal NVP -> network connection -> returns

I want the unit test to return a fake value instead of calling the Payment::GetToken method

The docs are not clear to me as to whether you can mock an entire class, replacing all its method calls.  It *seems* to say that, but I can't get it to work.

Following is my test project to try to understand what I should be doing. "testMain" is where the interesting part is, and it doesn't work.

TIA

public class HelloWorld {

    /**
     * @param args
     */
    public static void main(String[] args) {

        HelloWorld hello = new HelloWorld();

        hello.SayHello();
    }

    public void SayHello() {

        Printer print = new Printer();

        print.doPrint("Hello World");
    }

}
public class HelloWorldTest {

    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception {

        Mockito.reset();
    }

    /**
     * Test method for {@link HelloWorld#main(java.lang.String[])}.
     */
    @Test
    public void testMain() {

        Printer printerMock = mock(Printer.class);
        Mockito.when(printerMock.CleanString(Mockito.anyString())).thenReturn(MyCleanString());

        HelloWorld hello = new HelloWorld();

        hello.SayHello();
    }

    /**
     * @return
     */
    private String MyCleanString() {

        return "UNIT TEST";
    }
}
public class Printer {

    public void doPrint(String mesg) {

        System.out.println(CleanString(mesg));
    }

    public String CleanString(String mesg) {

        return mesg;
    }
}

Tomek Kaczanowski

unread,
Jun 26, 2013, 4:41:19 AM6/26/13
to moc...@googlegroups.com
Hi,

you create mock of a Printer but you never tell HelloWorld to actually use it.

The problem is that you create the printer within the tested method
(using "new").

There were few posts on this mailing list explaining what options you
have when dealing with "new".

--
Regards / Pozdrawiam
Tomek Kaczanowski
http://practicalunittesting.com

Eric Lefevre-Ardant

unread,
Jun 26, 2013, 5:26:08 AM6/26/13
to moc...@googlegroups.com
It would have been nice to have a failing test to make clear what "doesn't work"... anyway, I assumed that your problem is that you want to ensure that the actual message printed out is "UNIT TEST".

In short: you cannot do that without changing your code significantly. You need two things: 1) a way to use a mocked version of the Printer (which forbids the instantiation inside your production code), and 2) a way to catch the call to System.out.println().

Another problem is that you are trying to observe two levels of objects in a single test. This goes contrary to the idea behind mocks (and encapsulation in general), where you not want to know the insides of Printer from the HelloWorld class.

Another option is to assume that Printer is a simple wrapper over System.out.println(), so simple that it does not need to be tested. This means that you need to pull CleanString() out of it.

Finally, if you want to specify how CleanString work, you need to pull it into a some other service.

Here is something I might have done in your situation:

public class HelloWorld {
    private final Printer printer;

    public HelloWorld(Printer printer) {
        this.printer = printer;
    }

    public static void main(String[] args) {
        HelloWorld hello = new HelloWorld(new Printer(System.out, new StringCleaner()));
        hello.SayHello();
    }

    public void SayHello() {
        printer.doPrint("Hello World");
    }
}

public class Printer {
    private final PrintStream out;
    private final StringCleaner stringCleaner;

    public Printer(PrintStream out, StringCleaner stringCleaner) {
        this.out = out;
        this.stringCleaner = stringCleaner;
    }

    public void doPrint(String mesg) {
        out.println(stringCleaner.CleanString(mesg));
    }
}

public class StringCleaner {
    public String CleanString(String mesg) {
        return mesg;
    }
}

public class HelloWorldTest {
    private Printer printerMock = mock(Printer.class);
    private HelloWorld hello = new HelloWorld(printerMock);

    @Test
    public void should_send_hello_world_to_the_printer() {
        hello.SayHello();

        verify(printerMock).doPrint("Hello World");
    }
}

public class PrinterTest {
    private PrintStream printStream = mock(PrintStream.class);
    private StringCleaner stringCleaner = mock(StringCleaner.class);
    private Printer printer = new Printer(printStream, stringCleaner);

    @Test
    public void should_print_cleaned_up_string() {
        when(stringCleaner.CleanString("text")).thenReturn("clean text");

        printer.doPrint("text");

        verify(printStream).println("clean text");
    }
}


--
You received this message because you are subscribed to the Google Groups "mockito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+u...@googlegroups.com.
To post to this group, send email to moc...@googlegroups.com.
Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Russell Robinson

unread,
Jun 27, 2013, 5:48:05 PM6/27/13
to moc...@googlegroups.com
Hi,

Thanks for your replies.

I still cannot get an answer to this question - despite hours of reading the scant documents, and forums, and blog posts.

Which suggests I'm on a tangential plane to Mockito's concepts.

The question is quite simple: can Mockito stub at the class level instead of the object level?

I think the answer is no.  But, hopefully I can get a definitive answer...

The problem is with legacy code.  We need to intercept a particular method call that's made on an object somewhere deep in the method call sequence.

We know the class name at the top of the sequence, but we don't have access to the actual object instance because it's a local variable deep in the call sequence.

We don't want to refactor the code just so it can be tested (the code works and is in production).

Eric asked for a failing test, so here's the same test project with such a test.

It's this line that's the issue:
Mockito.when(printerMock.CleanString(Mockito.anyString())).thenReturn(MyCleanString());

what I really want to say is:
Mockito.when("class Printer".CleanString(Mockito.anyString())).thenReturn(MyCleanString());

meaning:
"When there is any call to the method CleanString in any object instance of the class Printer, then replace it with a call to MyCleanString()".
 
Thanks again!

public class HelloWorldTest {

    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception {

        Mockito.reset();
    }

    /**
     * Test method for {@link HelloWorld#main(java.lang.String[])}.
     */
    @Test
    public void testMain() {

        Printer printerMock = mock(Printer.class);
        Mockito.when(printerMock.CleanString(Mockito.anyString())).thenReturn(MyCleanString());

        HelloWorld hello = new HelloWorld();

        assertEquals("UNIT TEST", hello.WhatWillYouSay());

        hello.SayHello();
    }

    /**
     * @return
     */
    private String MyCleanString() {

        return "UNIT TEST";
    }
}
public class Printer {

    public void doPrint(String mesg) {

        System.out.println(SayWhat(mesg));
    }

    public String SayWhat(String mesg) {

        return CleanString(mesg);
    }

    public String CleanString(String mesg) {

        return mesg;
    }
}
public class HelloWorld {

    String mesg = "Hello World";

    /**
     * @param args
     */
    public static void main(String[] args) {

        HelloWorld hello = new HelloWorld();

        hello.SayHello();
    }

    public void SayHello() {

        Printer print = new Printer();

        print.doPrint(mesg);
    }

    public String WhatWillYouSay() {

        Printer print = new Printer();

        return print.SayWhat(mesg);
    }
}

Eric Lefevre-Ardant

unread,
Jun 28, 2013, 4:22:25 AM6/28/13
to moc...@googlegroups.com
So, the short answer is that Mockito does *not* allow this sort of things.

My understanding is that Mockito is a tool that helps you design your code, as long as you use it to create your code. Alternatively, you can think of it as "if this is hard to do with Mockito, it must be because the code is poorly designed". That's how I approach it anyway.

Clearly, it is not the goal of Mockito (or its direct competitors such as EasyMock or JMock) to make it easier to cut links between a part of the code and the rest of the application.

You might find what you want in PowerMock or others. I cannot help much here, as I don't use them at all.


Reply all
Reply to author
Forward
0 new messages