0

I have a domain class, which looks like this:

public class Employee
{
    public Guid EmployeeId { get; private set; }
    public string Name { get; private set; }
    public string Surname { get; private set; }
    ...
    // other properties

    public ICollection<Language> Languages { get; private set; }
        = new List<Language>();

    public ICollection<Skill> Skills { get; private set; }
        = new List<Skill>();

    public void AddLanguage(Language language)
    {
        if (language == null)
            return;

        Languages.Add(language);
    }

    public void DeleteLanguage(Guid languageId)
    {
        var languageToDelete = Languages
            .SingleOrDefault(x => x.LanguageId == languageId);

        if(languageToDelete == null)
            throw new ArgumentException("Language entry doesn't exist.");

        Languages.Remove(languageToDelete);
    }
}

I would like to test given methods but I'm stuck.

I have:

    [Fact]
    public void AddLanguage_AfterCallWithValidObject_LanguagesCollectionContainsAddedObject()
    {
        var language = new Mock<Language>();
        var employee = new Employee("Name", "Surname", ...);

        employee.AddLanguage(language.Object);

        Assert.Contains(employee.EmployeeLanguages, x => x.Language.Equals(language.Object));
    }

    [Fact]
    public void DeleteLanguage_WhenLanguageWithGivenIdDoesntExist_ThrowArgumentException()
    {
        var languageToDelete = new Language("English");

        var employee = new Mock<Employee>();
        employee.Setup(x => x.Languages).Returns(new List<Languages>
        {
            new Language("Spanish"),
            new Language("German")
        });

        employee.Object.DeleteLanguage(languageToDelete);

        // Asserts here
    }

In the first test I would like also assert that Languages.Add(skill) method was called but I have no idea how to do it.

  1. Is it an elegant way to do it? I thought about mocking Add method but I'm not sure if it is a good idea.

In the second test I cannot simply mock Employee object as it is not an interface. I thought about exposing Employee but I read I should not do that just for testing purpose.

  1. How should I mock Languages property without exposing Employee as interface? Is it possible? Is it any good practice to do this kind of things?
  2. Is my general concept for test these methods is okay? (I'm new in unit testing)
3
  • Assuming you are using Moq, you would have to make those methods virtual, and then create a mock of the domain object, do a proper Setup on the method(s), and then assert with a call to Verify. Commented Oct 2, 2018 at 18:22
  • Thanks. That was helpful. Commented Oct 2, 2018 at 19:02
  • 2
    why even mock this? Commented Oct 2, 2018 at 21:15

2 Answers 2

3

You should tests objects as "black box" without relying on implementation details. In your case implementation details is that Employee class uses ICollection.Add method.

And you definitely don't need to mock at all in your case.
Mock only dependencies which will make tests slow or very very very very complex to configure for the test.

[Fact]
public void AddLanguage_ShouldSaveGivenLanguage()
{
    var language = new Language();
    var employee = new Employee("Name", "Surname");

    employee.AddLanguage(language);

    var expectedLanguages = new[] { language };
    employee.EmployeeLanguages.Should().BeEquivalentTo(expectedLanguages);
}

Use public API Employee class provide to setup it for the test (back box).
For testing DeleteLanguage add dummy languages through public API of the class.

[Fact]
public void DeleteLanguage_WhenLanguageExists_Remove()
{
    var language1 = new Language("German");
    var language2 = new Language("French");
    var languageToDelete = new Language("English");

    var employee = new Employee("Name", "Surname");
    employee.AddLanguage(language1);
    employee.AddLanguage(language2);
    employee.AddLanguage(languageToDelete);

    employee.DeleteLanguage(languageToDelete);

    var expectedLanguages = new[] { language1, language2 };
    employee.EmployeeLanguages.Should().BeEquivalentTo(expectedLanguages);
}

[Fact]
public void DeleteLanguage_WhenLanguageNotExists_ThrowException()
{
    var language1 = new Language("German");
    var language2 = new Language("French");
    var notExistedLanguage = new Language("English");

    var employee = new Employee("Name", "Surname");
    employee.AddLanguage(language1);
    employee.AddLanguage(language2);

    Action delete = () => employee.DeleteLanguage(languageToDelete);

    delete.Should()
          .Throw<ArgumentException>()
          .WithMessage("Language entry doesn't exist.");
}

Did you notice how cumbersome employee.EmployeeLanguages reads, you can rename property to just employee.Languages.

For readable assertions I used FluentAssertions library

Sign up to request clarification or add additional context in comments.

Comments

1

For domain objects with very simple behavior and no dependencies, mocking isn't strictly needed. You can test Add simply with this:

//Arrange
var e = new Employee();
var l = new Mock<Language>(); 

//Act
e.AddLanguage(l.Object);

//Assert
Assert.IsTrue(e.Languages.Contains(l.Object));

Testing in this fashion you can achieve perfectly good code coverage and plenty of confidence that the Employee class works as designed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.