Reputation: 95
I have a game that has coins. There is an Add() method that changes the coins. I want to test if it adds correctly.
public class CoinsService
{
public ReactiveProperty<long> Coins { get { return State.SaveGame.Coins; } }
public int Add(int coins)
{
Coins.Value += coins;
return coins;
}
}
The test:
public class CoinServiceTests
{
[Test]
public void AddCoins_WhenCalled_AddsUpToTotalCoins()
{
var coinsService = new CoinsService();
coinsService.Add(10);
Assert.That(coinsService.Coins.Value, Is.EqualTo(10));
}
}
I have tried making a substitute of the class using NSubstitute like that:
var coinsService = Substitute.For<CoinsService>();
and instantiating a fresh instance of the Coins like that
coinsService.Coins.Returns(new ReactiveProperty<long>());
also like that
var coins = new ReactiveProperty<long>();
coinsService.Coins.Returns(coins);
I expect that that after doing any of the above, I will be able to check the Coins value.
Instead I'm getting a null reference object exception that the coinsService.Coins
object is null
To clarify, the null reference appears on the line
public ReactiveProperty<long> Coins { get { return State.SaveGame.Coins; } }
Upvotes: 0
Views: 739
Reputation: 2588
Don't mock any object shall be tested. Mocked instance is used for something "I tested it, but I need a fake one for another test"
In your case, what State.SaveGame.Coins
shall be mocked but not CoinsService
, because CoinsService
is going to be tested.
To make CoinsService testable, most common way is refactor CoinsService to accept Dependency Injection container and get rid of static State
.
// An interface to provide Coins
public interface ICoinProvider
{
ReactiveProperty<long> Coins{ get; }
}
public class CoinsService{
// Ctor take the instance in it
public CoinsService( ICoinProvider provider )
{
_CoinProvider = provider;
}
private ICoinProvider _CoinProvider;
public CoinWrap Coins
{
get
{
return _CoinProvider.Coins;
}
}
public int Add( int coins )
{
Coins.Value += coins;
return coins;
}
}
Then mock ICoinProvider
[Test]
public void AddCoins_WhenCalled_AddsUpToTotalCoins()
{
var mock = Substitute.For<ICoinProvider>();
mock.Coins.Returns( new ReactiveProperty<long>( 10 ) );
var coinsService = new CoinsService( mock );
coinsService.Add(10);
Assert.That(coinsService.Coins.Value, Is.EqualTo(10));
}
If you found this refactor is hard to be done, then you may read Charlie's post.
Upvotes: 1
Reputation: 13736
You are trying to test CoinsService
because it does the addition. Therefore, you have to use a real CoinsService
rather than mocking it. Mocking is for classes that collaborate with the class you are trying to test.
Looking at your code, I see why you thought this should work... you have this line of code...
coinsService.Coins.Returns(new ReactiveProperty<long>());
This causes a new Coins
property to be created each time it is accessed, which is why you have the error you are seeing.
I suspect that the root cause of this is that CoinsService
is a large class, with lots of functionality, which you don't want to instantiate just to test the ability to add coints. That leads to wanting to mock it. (If that's not true, we can stop here - just don't mock it!)
If CoinsService
is "too big to test", then it needs to be broken down in such a way that it uses collaborators. For example, imagine that Coins
were a class than just a long. It could have an Add method... Add(long howmuch)
for example.
Then CoinsService
would be changed just a bit to do...
public int Add(int coins)
{
Coins.Add(coins);
return Coins.Value; // I believe your original return is in error
}
Now this makes it all a bit more indirect, but gives you the advantage that you can test the addition function by testing the Coins class, without using a service.
You can (and should) also test the service itself by creating a mock for Coins and ensuring that its Add
method is called when CoinService.Add
is called.
Upvotes: 3