Reputation: 3
I am trying to unit-test the method Execute() in my InsertCashTransaction class. I want to test if it correctly assigns a new value to a User.Balance. You can see the two classes here
InsertCashTransaction class
public class InsertCashTransaction : Transaction
{
private IUser _userI;
public InsertCashTransaction(User user, DateTime date, decimal amount) : base(user, date, amount)
{
User = user;
}
public InsertCashTransaction(IUser UserI)
{
this._userI = UserI;
}
public override string ToString()
{
return $"Transaction number: {TransactionId}, Date: {Date}: {Amount} has been inserted onto {User.Username}'s wallet.";
}
// Method I am trying to test
public override void Execute()
{
if (Amount > 0)
{
User.Balance = User.Balance + Amount;
}
else if (Amount <= 0)
{
throw new ArgumentException("Not allowed to withdraw from users balance nor insert 0");
}
}
}
User class
public class User : IUser
{
private int _userid;
private string _firstname;
private string _lastname;
private string _username;
private string _email;
private decimal _balance;
public int UserID
{
get { return _userid; }
set
{
if (value < 1)
{
throw new ArgumentException("ID cannot be below one");
}
_userid = value;
}
}
public string FirstName
{
get { return _firstname; }
set
{
CheckIfNull(value);
ValidateName(value);
_firstname = value;
}
}
public string LastName
{
get { return _lastname; }
set
{
CheckIfNull(value);
ValidateName(value);
_lastname = value;
}
}
public string Username
{
get { return _username; }
set
{
CheckIfNull(value);
foreach (char item in value)
{
if (char.IsUpper(item))
{
throw new ArgumentException("Username is not allowed to hold use upper case letter");
}
if (char.IsSymbol(item))
{
throw new ArgumentException("Username must not contains symbols");
} else if (item == '-')
{
throw new ArgumentException("Username must not contain symbols");
}
}
_username = value;
}
}
public string Email
{
get { return _email; }
set
{
CheckIfNull(value);
//Creates two out of the email separated by @
string[] separation = value.Split('@');
string localPart = separation[0];
string domain = separation[1];
foreach (char item in localPart)
{
if (char.IsLetterOrDigit(item) == false)
{
if (item != '.' || item != '-' || item != '_' || item != ',')
{
continue;
}
else
{
throw new ArgumentException("Not a valid email");
}
}
}
// Check if domain starts with '.' or '-'
if (domain.Contains("."))
{
if (domain.StartsWith(".") || domain.StartsWith("-") || domain.EndsWith(".") || domain.EndsWith("-"))
{
throw new ArgumentException("domain must not start with .");
}
}
foreach (char item in domain)
{
if (char.IsSymbol(item))
{
throw new ArgumentException("Domain must not contain any symbols");
}
}
_email = value;
}
}
public decimal Balance
{
get { return _balance; }
set
{
if (value < 0)
{
throw new ArgumentException("Balance is below 0");
}
_balance = value;
}
}
public override string ToString()
{
return $"{FirstName}, {LastName}, {Email}";
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (this.GetType() != obj.GetType())
{
return false;
}
return Equals((User)obj);
}
public bool Equals(User obj)
{
if (obj == null)
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (this.GetHashCode() != obj.GetHashCode())
{
return false;
}
System.Diagnostics.Debug.Assert(base.GetType() != typeof(object));
if (!base.Equals(obj))
{
return false;
}
return UserID.Equals(obj.UserID);
}
public override int GetHashCode()
{
return UserID.GetHashCode();
}
public int CompareTo(User user)
{
if (UserID > user.UserID)
{
return -1;
}
return 1;
}
public User(int id, string firstName, string lastName, string username, string email, decimal balance)
{
UserID = id;
FirstName = firstName;
LastName = lastName;
Username = username;
Email = email;
Balance = balance;
}
public User()
{
}
public string CheckIfNull(string element)
{
if (string.IsNullOrEmpty(element))
{
throw new ArgumentNullException("Something is missing");
}
return element;
}
protected string ValidateName(string name)
{
foreach (char item in name)
{
if (char.IsDigit(item))
{
throw new ArgumentException("Something is wrong in either firstname or lastname");
}
}
return name;
}
}
I've so far tried to create an interface of the User class and via Nsubsitute tried to make a substitute of the class in the test which you can see here
Iuser interface
public interface IUser
{
int UserID { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
string Username { get; set; }
string Email { get; set; }
decimal Balance { get; set; }
}
InsertCashTransactionTest class
[TestFixture]
class InsertCashTransactionTest
{
[TestCase(0)]
[TestCase(-1)]
[TestCase(-10)]
[TestCase(-50)]
public void AmountBelowZero_throwException(decimal number)
{
IUser user = Substitute.For<IUser>();
InsertCashTransaction icTransaction = new InsertCashTransaction(user);
icTransaction.Amount = number;
Assert.Catch<ArgumentException>(() => icTransaction.Execute());
}
// Test that isn't working
[TestCase(1)]
[TestCase(10)]
[TestCase(50)]
public void AmountAboveZero_InsertToUserBalance(decimal number)
{
//Arrange
IUser user = Substitute.For<IUser>();
InsertCashTransaction icTransaction = new InsertCashTransaction(user);
user.Balance = 0;
icTransaction.Amount = number;
decimal actualresult = number;
// Act
// Somewhere here it goes wrong
icTransaction.Execute();
//Assert
Assert.AreEqual(actualresult, user.Balance);
}
My actual problem When I try to run the test I get a Nullreference exception and my problem is that I don't know where or what I am doing wrong. Problem seems to be whenever icTransaction.Execute() gets called. I hope that you can help me figure out what I am doing wrong.
Please ask if something is unclear and needs further explaining
Upvotes: 0
Views: 89
Reputation: 10484
From the posted code I do not think mocking User
is necessary in this case. Mocking is mainly to substitute for classes that are difficult to test due to certain side-effects, non-determinism, dependencies or speed.
Examples include sending an email, or requiring a full database setup, or simulating varying network conditions, etc.
As well as addressing the points @Cleriston made, I suggest dropping the IUser
interface and instead use a real User
instance. You can then write a test for the real logic using something like:
[TestCase(1)]
[TestCase(10)]
[TestCase(50)]
public void AmountAboveZero_InsertToUserBalance(decimal number)
{
//Arrange
var user = new User(1, "FirstName", "Surname", "tst", "[email protected]", 0);
var icTransaction = new InsertCashTransaction(user, DateTime.Now, number);
// Act
icTransaction.Execute();
//Assert
Assert.AreEqual(number, user.Balance);
}
Upvotes: 1
Reputation: 770
You seem to be having scope problems.
Your constructor requires: User, Date and Amount as parameters. When you initialise your class only with User, the other params get null. Check it out.
[TestCase(1)]
[TestCase(10)]
[TestCase(50)]
public void AmountAboveZero_InsertToUserBalance(decimal number)
{
//Arrange
IUser user = Substitute.For<IUser>();
InsertCashTransaction icTransaction = new InsertCashTransaction(user, null, number);
user.Balance = 0;
decimal actualresult = number;
// Act
// Somewhere here it goes wrong
icTransaction.Execute();
//Assert
Assert.AreEqual(actualresult, user.Balance);
}
note avoid declaring variables with native type name. ex: number, boolean, integer, etc
User base to reference parents (good practice and clean code):
if (base.Amount <= 0)
{
throw new ArgumentException("Not allowed to withdraw from users balance nor insert 0");
}
User.Balance = User.Balance + base.Amount;
When you are lost where the error might be. Check the output of your console, where you can find the file, line and position of your error. Also sharing the error output error would've made easier to track your problem.
Upvotes: 0