Mocking a method with a side effect on its argument

Viewed 335

I've got a method in SomeRepository that is like follows:

public async Task<Guid> InsertAsync(TEntity entity)
        {
            await _context.Set<TEntity>().AddAsync(entity);
            await _context.SaveChangesAsync();
            return entity.Id;
        }

_context variable here is just an instance of a concrete implementation of EntityFrameworkCore DbContext.

Now there are some methods that I'd like to test and that use this method in a following manner:

public async SomeDto SomeMethod(){

    var guid = await someRepository.InsertAsync(entity);
    //...

    //A bit of code that now relies on entire entity object and it's Id, which gets assigned same value as guid variable  - this is a consequence of a side effect of InsertAsync, illustrated with this line
    var someDto = new SomeDto(entity); 
   
    

    return someDto;
    }

As I'm using Moq, I can mock this when testing like follows:

     // setup
     var guid = Guid.NewGuid();
     someRepositoryMock.Setup(s => s.InsertAsync(It.IsAny<Data.Entities.SomeEntity>()))
                .Returns<Data.Entities.SomeEntity>((a) => 
                {
                    a.Id = guid;
                    return Task.Run(() => a.Id); } 
                );
    var someClass = new SomeClass(someRepositoryMock.Object)
    
    //act - this is a method I'd like to unit test    
    var result = await someClass.SomeMethod();

    //assert - here
    Assert.Equal(guid, result.Id);
    //other asserts on result values here
    

Now, given this code as an example, the question is when you have a method you'd like to unit test, and it is reliant on a side effect of it's owner's dependency, is it really a good idea to recreate the side effect of a dependency in it's mock?

My justification for doing this is that by mocking this dependency along with all it's methods' side effects, I'm effectively isolating all aspects of this dependency that is of significance to the method I'm testing.

However, I have a hunch that something is not right here. It feels as if the same functionality - the mentioned side effect is just rewritten in the mock. The recreation of a side effect is of course very simple in this example, but a scenario is easily imagined where it can be arbitrarily complex and much tougher to recreate.

Ideally, all functions would be pure, and this problem would be gone. But this is not the case.

1 Answers

I agree that there is a smell in the code. I think the issue is that SomeMethod performs a sideeffect that alters the state of the instance, i.e. alters the id of the field entity and then also performs logic - which is omitted here.

SomeMethod seems to insult "Single Level of Abstraction" principle. And thus it is unclear to me what you intend to test. From what I see here you test the constructor of SomeDto - which is a good idea but should be done directly.

So my advise is to test the repository separately and the constructor of SomeDto separately. Moreover I would extract the logic you omitted in your example and unit test this as well.

The rest could then be subject to integration tests.

Related