0

I am using SpringBoot 1.5.9 and try to do Integration testing. Weirdly a MongoRepository.save() method updates the object when called on mock MongoRepository.

I have a Counter Class

public class Counter {
     public String id;
     public int seq;

     public void increaseSeq() {
        this.seq += 1;
     }
}

And his repository

public interface CounterRepository extends MongoRepository<Counter, String>{
    Counter findById(String id);
    List<Counter> findAll();
}

And his service

@Service
public class CounterService {
    @Autowired private CounterRepository counterRepository;


    public Counter findCounter(String id) {
        return counterRepository.findById(id);
    }

    public int getSeqAndIncrease(String id) {
        Counter counter = findCounter(id);
        if (counter == null) {
            return -1;
         }

        counter.increaseSeq();

        counterRepository.save(counter);

        return counter.getSeq();
    }
}

Now, when I do system integration and try to mock the counterRepository, it happens something that I don't expect. The counterRepository.findById() returns a Counter object where the 'seq' field is increased. Why? Does the counterRepository.save() affect the result in any way (the counterRepository is mocked, hence I suppose that save() should not have any effect)?

@RunWith(SpringRunner.class)
@SpringBootTest
public class FlowServiceTest {
    @MockBean private CounterRepository counterRepository;
    @Autowired private CounterService counterService;

    @Before
    public void setUp() {  
     Mockito.when(counterRepository.save(any(Counter.class))).then(arg -> arg.getArgumentAt(0, Counter.class));
     Mockito.when(counterRepository.findById("flow")).thenReturn(new Counter("flow", 10));
    }

    @Test
    public void testSavingInDatabase() {
        System.out.println(counterRepository.findById("flow"));

        counterService.getSeqAndIncreaseSafe("flow");
        System.out.println(counterRepository.findById("flow"));

        counterService.getSeqAndIncreaseSafe("flow");
        System.out.println(counterRepository.findById("flow"));
    }
}

It prints "10 11 12". Why doesn't it print '10 10 10'?

Andrei Margeloiu
  • 849
  • 2
  • 12
  • 17

2 Answers2

0

The problem is these lines

counterRepository.save(counter);
return counter.getSeq();

What you should be doing is this

Counter saveCounter = counterRepository.save(counter);
return savedCounter.getSeq();

In getSeqAndIncrease method, you are not returning sequence of the saved object.
By doing this you are making your mock statement for save useless. Because you are not using the value returned from mock.

pvpkiran
  • 25,582
  • 8
  • 87
  • 134
  • It still does not work. I believe that the problem comes from Mockito. When setting 'Mockito.when(counterRepository.findById("flow")).thenReturn(new Counter("flow", 10));', it might seem intuitive to return a new object everytime, but the return object is initialised only once when the test starts and will be returned at all subsequent calls. – Andrei Margeloiu Feb 21 '18 at 09:54
  • Then, in my code I do counter.increaseSeq(); which increases the 'seq' of found object (this object comes from Mockito). Then at the next call, Mockito returns the firstly initialised object which was updated in the meantime; Mockito does not return a new object as it might seem like. – Andrei Margeloiu Feb 21 '18 at 09:54
  • Yes, returned object is initialized only once in mockito. So you basically get the same reference everytime, and since it is a reference not a new object, the values are updated – pvpkiran Feb 21 '18 at 10:12
0

tl;dr - The returned object from mock is initialized only once in mockito. So I basically got the same reference every time, and since it is a reference not a new object, the values are updated.

Complete answer: When setting

Mockito.when(counterRepository.findById("flow")).thenReturn(new Counter("flow", 10));

, it might seem intuitive to return a new object every time, but the return object is initialised only once when the test starts and will be returned at all subsequent calls.

Then, in my code I do

counter.increaseSeq(); 

which increases the 'seq' of found object (this object comes from Mockito). Then at the next call, Mockito returns the firstly initialised object which was updated in the meantime; Mockito does not return a new object as it might seem like.

Andrei Margeloiu
  • 849
  • 2
  • 12
  • 17