Skip to content

Commit

Permalink
docs update, unit-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorium committed Feb 29, 2024
1 parent f15bf53 commit 7c75b43
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 471 deletions.
3 changes: 2 additions & 1 deletion docs/content/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</button>

<div class="collapse navbar-collapse navbar-nav-scroll" id="navbarsExampleDefault">
<a href="http://github.com/fsprojects/SQLProvider"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<a href="http://github.com/fsprojects/SQLProvider"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://github.blog/wp-content/uploads/2008/12/forkme_right_gray_6d6d6d.png?resize=149%2C149" alt="Fork me on GitHub"></a>
<div style="height: 105px"></div>

<ul class="navbar-nav">
Expand All @@ -71,6 +71,7 @@
<li class="nav-item"><a href="{{fsdocs-package-project-url}}/core/postgresql.html" class="nav-link">PostgreSQL</a></li>
<li class="nav-item"><a href="{{fsdocs-package-project-url}}/core/mysql.html" class="nav-link">MySQL</a></li>
<li class="nav-item"><a href="{{fsdocs-package-project-url}}/core/odbc.html" class="nav-link">ODBC</a></li>
<li class="nav-item"><a href="{{fsdocs-package-project-url}}/core/unittest.html" class="nav-link">Unit-testing</a></li>
<li class="nav-item"><a href="{{fsdocs-package-project-url}}/reference/index.html" class="nav-link">Internal API Docs</a></li>

{{fsdocs-list-of-namespaces}}
Expand Down
2 changes: 1 addition & 1 deletion docs/content/content/fsdocs-custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ blockquote {
}
/* keywords */
#fsdocs-content span.k {
color: #2248c4;
color: #1f4ee9;
}
/* comment */
#fsdocs-content span.c {
Expand Down
156 changes: 156 additions & 0 deletions docs/content/core/unittest.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
(*** hide ***)
#r "../../../bin/netstandard2.0/FSharp.Data.SqlProvider.dll"
(*** hide ***)
let [<Literal>] resolutionPath = __SOURCE_DIRECTORY__ + @"/../../files/sqlite"
(*** hide ***)
let [<Literal>] connectionString = "Data Source=" + __SOURCE_DIRECTORY__ + @"\..\northwindEF.db;Version=3;Read Only=false;FailIfMissing=True;"

open FSharp.Data.Sql
(*** hide ***)
type sql = SqlDataProvider<
Common.DatabaseProviderTypes.SQLITE,
connectionString,
SQLiteLibrary=Common.SQLiteLibrary.SystemDataSQLite,
ResolutionPath = resolutionPath,
CaseSensitivityChange = Common.CaseSensitivityChange.ORIGINAL
>
(*** hide ***)
open FSharp.Data.Sql
(*** hide ***)
let assert_equal x y = ()
(*** hide ***)
let assert_contains x y = ()
(**
# SQLProvider allows you to unit-test your SQL-logic
That's a clear advantage on large-scale projects, where there are multiple developers and
the SQL-queries grow more complex over time.
1. Debugging. Faster to debug unit-test than spin the full environment again and again.
2. Refactoring: To ensure what the original functionality is actually doing before you modify.
## Why to unit-test?
F# strong typing provides safety over raw SQL: Instead of your customer finding an issue, your code will not compile if the database shape is wrong,
for example someone removed an important column.
SQLProvider does parametrized SQL, you can watch the executed SQL, and you can even open the parameterized SQL parameters for easier debugging:
*)
FSharp.Data.Sql.Common.QueryEvents.SqlQueryEvent
|> Event.add (fun e -> System.Console.WriteLine (e.ToRawSqlWithParamInfo()))
(**
But unit-testing is good addition in scenarios where:
- Your database is very dynamic, and data is changing all the time
- You want to ensure the logic working over period of time
- You have a big project where build-time takes long
- You want Continuous Integration, but your test-data or database is not stable.
## How?
There are 2 helper functions to mock the database connection:
- `FSharp.Data.Sql.Common.OfflineTools.CreateMockEntities<'T>` - With this you can mock a single table.
- `FSharp.Data.Sql.Common.OfflineTools.CreateMockSqlDataContext<'T>` - With this you can mock a context with multiple tables
You just feed an anonymous records like they would be database rows.
You don't need to add all the columns, just the ones you use in your query.
But you can add extra-columns for easier asserts.
### Example, executable business logic
*)

open System

type OrderDateFilter =
| OrderDate
| ShippedDate
| Either

let someProductionFunction (ctx:sql.dataContext) (orderType:OrderDateFilter) (untilDate:System.DateTime) =
task {
let ignoreOrderDate, ignoreShippedDate =
match orderType with
| OrderDate -> false, true
| ShippedDate -> true, false
| Either -> false, false

let tomorrow = untilDate.AddDays(1.).Date
let someLegacyCondition = 0 // we don't need this anymore

let itms =
query {
for order in ctx.Main.Orders do
join cust in ctx.Main.Customers on (order.CustomerId = cust.CustomerId)
where ((cust.City = "London" || cust.City = "Paris" ) && (
(ignoreOrderDate || order.OrderDate < tomorrow) && (someLegacyCondition < 15)) &&
(ignoreShippedDate || order.ShippedDate < tomorrow) &&
cust.CustomerId <> null && order.Freight > 10m
)
select (cust.PostalCode, order.Freight)
}
let! res = itms |> Array.executeQueryAsync

//maybe some post-processing here...
return res
}

(**
### Example, unit-test part
Note: CustomerID, not CustomerId. These are DB-field-names, not nice LINQ names.
*)
let ``mock for unit-testing: datacontext``() =
task {
let sampleDataMap =
[ "main.Customers",
[| {| CustomerID = "1"; City = "Paris"; PostalCode = "75000"; Description = "This is good"; |}
{| CustomerID = "2"; City = "London"; PostalCode = "E143AB"; Description = "This is good"; |}
{| CustomerID = "3"; City = "Espoo"; PostalCode = "02600"; Description = "Ignore this guy"; |}
|] :> obj
"main.Orders",
[| {| CustomerID = "1"; OrderDate = DateTime(2020,01,01); ShippedDate = DateTime(2020,01,04); Freight = 4m;|}
{| CustomerID = "1"; OrderDate = DateTime(2021,02,11); ShippedDate = DateTime(2021,02,12); Freight = 22m;|}
{| CustomerID = "2"; OrderDate = DateTime(2022,03,15); ShippedDate = DateTime(2022,03,22); Freight = 20m;|}
{| CustomerID = "2"; OrderDate = DateTime(2024,02,03); ShippedDate = DateTime(2024,02,17); Freight = 50m;|}
{| CustomerID = "3"; OrderDate = DateTime(2024,02,03); ShippedDate = DateTime(2024,02,17); Freight = 15m;|}
|] :> obj

] |> Map.ofList
let mockContext = FSharp.Data.Sql.Common.OfflineTools.CreateMockSqlDataContext<sql.dataContext> sampleDataMap

let! res = someProductionFunction mockContext OrderDateFilter.OrderDate (DateTime(2024,02,04))
//val res: (string * decimal) list =
// [("75000", 22M); ("E143AB", 20M); ("E143AB", 50M)]

assert_equal 3 res.Length
assert_contains ("75000", 22M) res
assert_contains ("E143AB", 20M) res
assert_contains ("E143AB", 50M) res
}

(**
CreateMockSqlDataContext takes a `Map<string,obj>` where the `string` is the table name as in database, and `obj` is an array of anonymous records.
The mock is meant to help creating data-context objects to enable easier testing of your LINQ-logic, not to test SQLProvider itself.
There are some limitations with the SQLProvider mock a DB-context:
- The mock-context will not connect the DB, and you can't save entity modifications. SubmitUpdates() will do nothing.
- SQLProvider custom operators (like `x |=| xs` and `y %<> "A%"`) are not supported. So you have to use LINQ-ones (e.g. `xs.Contains x` and `not y.StartsWith "A"`) that do work in SQLProvider as well.
- You cannot call database-table `.Create` methods to create new instances. (You can call update entity columns `x.Col <- Some "hi"`, but it doesn't really do anything.)
- You cannot call stored procedures.
- Names are database names, and they are case-sensitive. If you miss a table, in your mock, there will be clear error. If you mistyped anonymous record column name, you will probably just get a zero-result or ValueNone.Value-error or some other unwanted behaviour.
If you are running off-line solution like SSDT or ContextSchemaPath, you should be able to run also these unit-tests with your CI.
*)
1 change: 1 addition & 0 deletions docs/content/index.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ The library comes with comprehensive documentation.
* [Individuals](core/individuals.html) usage and limitations of this unique feature
* [Composable Query](core/composable.html) information on integrating this project with the SQL provider
* [Mapping to record types](core/mappers.html)
* [Unit-testing](core/unittest.html) your SQL-query logics without a database.
* [API Reference](reference/index.html) contains automatically generated documentation for all types, modules
and functions in the library.
Expand Down
2 changes: 1 addition & 1 deletion paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ group Docs
nuget FSharp.Formatting >= 4.0.0
nuget FSharp.Literate >= 4.0.0
nuget Dotnet.ProjInfo.Workspace.FCS
nuget FSharp.Compiler.Service 41.0.7
nuget FSharp.Compiler.Service 43.8.100

group Tests
source https://api.nuget.org/v3/index.json
Expand Down
Loading

0 comments on commit 7c75b43

Please sign in to comment.