There is a class that internally uses calls from the native library. Also inside the class there are private and public methods.

class Matrix { private List<IntPtr> _rows; [DllImport("MathCore.dll")] static private extern IntPtr CreateMatrixRow(string row); [DllImport("MathCore.dll")] static private extern IntPtr RemoveMatrixRow(int index); [DllImport("MathCore.dll")] static private extern void AddItem(IntPtr row, string name); [DllImport("MathCore.dll")] static private extern void RemoveItem(IntPtr row, string name); public Matrix(List<string> rows, string equation) { // some constructing ParseRows(rows); ParseEquation(equation); } private void ParseEquation(string equation) { // parse someIndex and someName AddItem(GetRow(someIndex), someName); } private void ParseRows(List<string> rows) { foreach (var row in rows) { _rows.Add(CreateMatrixRow(row)); } } public int GetRowCount() { return _rows.Count; } public int GetRow(int index) { return _rows[index]; } } 

What is the best way to split or change the implementation so that it can cover the Matrix class with UNITEST without calling the MathCore.dll library?

  • If I understand correctly, do you want to cover the tests from a third-party library, in particular MathCore ? - sp7
  • Cover with tests the class itself without calling MathCore. That is, somehow replace the externs on fakie. It is clear that it is necessary to divide the implementation, but it is not clear exactly how. - andreikashin

2 answers 2

Obviously, in order to test the logic enclosed directly in the Matrix class, you must somehow be able to "replace" the calls of a third-party library with your own values. This can be achieved in several ways:

  • A variant with an abstract class and overridden heirs proposed by @DreamChild. In my opinion, this approach is less flexible. For example, in order to set your own variants of return values ​​from the ParseEquation , ParseRows methods, ParseRows will have to either create separate heirs for each set of returned values, or use partial mocks, which not every mock framework can do.
  • Another option is to allocate third-party code to dependency. A dependency is some object that will be passed inside the Matrix class. One of the advantages of this approach is easier testing - there is no need to create mocks with your hands, everything is done with the help of the mock framework. You can read the overview in the question "Why is Dependency Injection needed?" , and delve into the book "Implementing dependencies in .NET" .

     public interface IMathCore { IntPtr CreateMatrixRow(string row); IntPtr RemoveMatrixRow(int index); void AddItem(IntPtr row, string name); void RemoveItem(IntPtr row, string name); } public class MathCore : IMathCore { [DllImport("MathCore.dll")] static private extern IntPtr CreateMatrixRow(string row); [DllImport("MathCore.dll")] static private extern IntPtr RemoveMatrixRow(int index); [DllImport("MathCore.dll")] static private extern void AddItem(IntPtr row, string name); [DllImport("MathCore.dll")] static private extern void RemoveItem(IntPtr row, string name); public IntPtr CreateMatrixRow(string row) { return CreateMatrixRow(row); } // методы интерфейса вызывают сторонние методы } public class Matrix { private readonly IMathCore _mathCore; private List<IntPtr> _rows; public Matrix(List<string> rows, string equation) : this(new MathCore()) // реализация по умолчанию { } public Matrix(List<string> rows, string equation, IMathCore mathCore) { _mathCore = mathCore; // some constructing ParseRows(rows); ParseEquation(equation); } private void ParseEquation(string equation) { // parse someIndex and someName AddItem(GetRow(someIndex), someName); } private void ParseRows(List<string> rows) { foreach (var row in rows) { _rows.Add(_mathCore.CreateMatrixRow(row)); } } public int GetRowCount() { return _rows.Count; } public int GetRow(int index) { return _rows[index]; } } 

    You can select an abstract class from the Matrix class. In it to take out the methods ParseEquation , ParseRows and others. At the same time, the methods exported from the library should be replaced by abstract methods with the same signatures and return values. Then from this class inherit the other two, not abstract. In the first one, declare the methods exported from the library (with other names, of course), and in the abstract methods inherited from the ancestor and implemented in this class, call these exported from the library. The second heir of the abstract class will not include methods exported from the library, and in the methods inherited from the abstract class you can write some kind of test implementation and using this class to safely test the business logic contained in the abstract class