Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Mocking methods with arguments

+1
−3

Let's say I have this class I want to mock

class A {
    public void add(T arg) {
        B b = A.getB();
        U val = somefunc(arg);
        V ret = b.add(val);
    }
}

I have a spy on A and a mock on B, but when A::foo calls B::bar, I want it to be mocked. But I want some stuff to be done inside it. Something like this:

class BMock {
    List<U> ulist = new ArrayList<>();

    R add(U u) {
        if(ulist.contains(u)) throw exception;
    }

    return new R(x);
}

But I'm having a bit of a problem how to do this. I'm looking for something on this form:

A a = spy(A.class);
B b = mock(B.class);
a.b = b;
BMock bm = new BMock();
doAnswer(bm.add(arg)).when(b).add(any());
                 ^                  |
                 |                  |
                  ------------------

What I want to do is to send the any argument over to bm.add

In the real code, B::add will contact a remote server to add an item. If it gets called twice with the same item, the server will return an error. A::add will analyze this return code and act accordingly. And this is what I'm trying to simulate. How should I do it?

EDIT

I got some suggestions in comment section. But I might add that I cannot change B because it's an external library, and the class B both misses default constructor and is final.

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

I'm trying to test your code, but I think there are some details missing. I understood that `A::add` ... (7 comments)

3 answers

You are accessing this answer with a direct link, so it's being shown above all other answers regardless of its score. You can return to the normal view.

+0
−0

You mean, aside from giving the team that made B a stern talking to? ;-)

You could introduce another layer of indirection, and mock that. For instance like this:

interface MockFriendlyB {
    public void add(T arg);
}
        
class UnmockedB implements MockFriendlyB {
    B b = ...;

    public void add(T arg) {
        b.add(arg);
    }
}

class MockedB implements MockFriendlyB {
    public void add(T arg) {
        // do whatever you like
    }
}

and then change A to use a MockFriendlyB rather than B, and give A an UnmockedB in normal usage, but a MockedB when testing.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

+0
−0

I managed to do it with lambda. Something like this:

Set<U> ulist = new LinkedHashSet<>();    

@Test
void test() {
    doAnswer(i -> {
        U arg = i.getArgument(0);
        if(ulist.contains(arg)) {
            return 0;
        }
        ulist.add(arg);
        return 1;
    }).when(b).add(any());
History
Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

+0
−0

Based on your answer, I guess you just wanted to have a list of unique arguments that were passed to B::add. In that case, you could use a Set instead of a List:

// "Type" is whatever type B:add receives as argument
Set<Type> calledArgs = new LinkedHashSet<>();

@Test
public void test() {
    doAnswer(i -> {
        Type arg = i.getArgument(0);
        calledArgs.add(arg);
        return null;
    }).when(b).add(any());
}

A Set doesn't allow duplicated elements, so you can just add them and the Set will only add elements that aren't already in the set. I've used a LinkedHashSet to keep the elements in the order they were inserted (but if you don't care about that, you could use a HashSet instead).

If you really want a list, though, just create one after the test:

// creates a List with all the Set's elements
List<Type> list = new ArrayList<>(calledArgs);

It's not clear what type B::add receives as argument (in your answer you create a List<U>, but then you add a T instance to that list), but anyway, just change Type to whatever type the method expects.


Another solution is to use an org.mockito.ArgumentCaptor to capture the arguments passed to B::add:

// setup "a" and "b" (the same way you're already doing)
B b = mock(B.class);
A a = spy(A.class);
a.b = b;

...

@Test
public void test() {
    // call a.add() many times, passing whatever type it expects
    int n = // ... number of times a.add() will be called
    for (int i = 0; i < n; i++) {
        a.add(whateverArgsItExpects);
    }

    // after the calls to a.add(), check the arguments that were passed to b.add()
    ArgumentCaptor<Type> captor = ArgumentCaptor.forClass(Type.class);
    verify(b, times(n)).add(captor.capture()); // <-- HERE: the ArgumentCaptor will capture all args passed to B::add

    // get all the values passed to b.add()
    List<Type> calledArgs = captor.getAllValues();
    // creates a Set with the values
    Set<Type> calledArgsNoRepeat = new LinkedHashSet<>(calledArgs);

    // check whatever you need in the List/Set
}

I'm assuming that Type is the object's class you're passing to B::add.

I'm using Mockito.verify (assuming you're using import static org.mockito.Mockito.*, as your code suggests) to check if B::add is being called as many times as A::add (assuming that every call to A::add will also call B::add, but you can adjust the number to your test cases). And the ArgumentCaptor will capture the arguments passed to B::add in all those calls.

Then I get the list of all the arguments that were passed to B::add (by using captor.getAllValues()) and create a Set with it, which eliminates the duplicates. Again, I used a LinkedHashSet to preserve the order, but a HashSet can also be used if you don't care about the order.

Obviously, this assumes that Type correctly implements equals and hashCode methods. If it doesn't, the set won't be able to check for duplicates (as your code uses List::contains, I'm assuming that those methods are correctly implemented).

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

Thanks (1 comment)

Sign up to answer this question »