This project contains an solution that gives an example on how to structure code in a .NET/C# codebase, to enable rather strict isolation of units during unit testing, and enable the implementation and usage of mock logic that can be used both during unit testing and development.
The point of this is to show a minimal example of a structure in a .NET solution that enables:
- Separation of concerns,
- Testability; and
- Mockability
The solution contains 5 basic C# projects that each fills a core role. In addition to these, there is one unit test project, one UI project and one UI test project. The core projects are:
- Common
- Models
- Logic.Interfaces
- Logic.Real
- Logic.Simulated
- (Test.Unit)
The (primary) dependency graph between these looks like:
+------------------+
| |
| Logic.Real |
| |
+--------|---------+
|
+--------+ +--------+ +--------v---------+
| | | | | |
| Common <----+ Models <----+ Logic.Interfaces |
| | | | | |
+--------+ +--------+ +--------^---------+
|
+--------|---------+
| |
| Logic.Simulated |
| |
+------------------+
N.B. the graph above depicts only the primary dependents. E.g. Logic.Real
depends on Logic.Interfaces
, because its primary purpose is to implement
interfaces found in that project. However, to do that, it needs the models
defined in Models
, and most likely elements that are found in the Common
project. Similarly, since some models might implement certain interfaces found
in Common
, Logic.Interfaces
have to have a dependency on Common
. This is
because the dependencies propagate down in the dependency chain. The Test.Unit
project depends on all core projects Test.Unit
contains the unit tests for
the logic found in the core projects. In a big project, it might be reasonable
to also create a Test.Integration
project, specifically to only contain code
that does automated integration tests.
In addition to the projects above, there are the following also:
- UI.Console
- UI.Console.Test
All of these depend on, at least, all of the core projects (except the
test projects). UI.Console
is the actual application that a user consumes.
Examples of other real world applications would be a UI.API.Web
project
and/or a UI.Graphical.Web
project. The UI.Console.Test
is a "Coded UI Test"
project, that emulates a human user interacting with the application via normal
HIDs.
Each of the above projects have a rather defined set of rules regarding what one should put, and not to put in them, as follows:
This project should contain generic functionality, that is agnostic to the solution's business requirements. Examples of this kind of functionality is:
- Extensions that operate on built-in types or own types defined in
Common
, - Exception types that can be used in common situations;
- Interfaces that can be used in common situations;
- etc.
All the Models used by the business logic should reside here. Examples include
database models, integration models, etc. Ideally, this project should not
contain any logic at all, only definitions. It would be reasonable to create a
sibling project Models.Integration
to contain models only relevant for
3rd party system integration, and limit this project to contain only own
database models.
Here, we put interfaces that define how we are able to interact with the
business logic. E.g. if we want to be able to authenticate users, we would
define a IUserService
or something like that in this project, and then we
would implement that interface in Logic.Real
and/or Logic.Simulated
.