SSW
RxJS refactor: BehaviourSubjects
2018-06-03CODING ·angular rxjs behavioursubject
Using RxJS BehaviourSubjects to tidy up our code and remove tight coupling between the Template and the component, whilst also providing hooks to trigger data updates.In an earlier post I described how to leverage the power of RxJS and the combineLatest() and startWith() operators to tidy up my code. I have since updated the code on that page to reflect the new RxJS6 pipe operators.
I was never really 100% happy with this solution for two reasons in particular: I still had to subscribe to the results (this was in part resolved with my follow-up post, and I had to reference the ViewChild elements in the component. Although Angular allows us to reference the view elements using the ViewChild() operator, this isn’t the cleanest solution. 1) It tightly couples the view template to the implementation and logic of the component and 2) When looking at the template, the event bindings aren’t obvious.
Enter BehaviourSubjects.
It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the “current value” from the BehaviorSubject.source
Kind of sounds like what we are trying to achieve with our searching and pagination. As a bonus, a BehaviourSubject takes an initial value. This now means that we can do away with our StartWith operator.
The original code currently looks like this
results-list.component.html
results-list.component.ts
items: result[];
totalPages: number;
readonly pageSize = 25;
@ViewChild(‘paginator’) paginator: PaginationComponent;
@ViewChild(‘filters’) filters: FilterComponent;
ngOnInit(){
const page$ = this.paginator.pageUpdated.pipe(
startWith(1),
tap(x= > this.currentPage = 1));
const filter$ = this.filter.filterUpdated.pipe(
startWith(”),
tap(x = > this.currentPage = x));
combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(200),
switchMap(r = > this.searchService.performSearch(r.page, r.filter)))
.subscribe(results = > {
this.items = results.items;
this.totalpages = results.totalItems / this.pageSize;});
}
Final code
results-list.component.html
results-list.component.ts
results$: Observable;
currentPage$: BehaviourSubject(1);
filter$: BehaviourSubject(null);
readonly pageSize = 25;
ngOnInit(){
items$ = combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(300),
switchMap(r = > this.searchService.performSearch(r.page, r.filter))
);
}
Another benefit this optimisation gives us is the ability to modify our filters and pagination from any source, not just from the components raising the original events.
results-list.component.ts
results$: Observable;
currentPage$: BehaviourSubject(1);
filter$: BehaviourSubject(null);
readonly pageSize = 25;
ngOnInit(){
items$ = combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(300),
switchMap(r = > this.searchService.performSearch(r.page, r.filter))
);
}
clearResults(){
this.currentPage$.next(1);
this.filter$.next(”);
}
Wow… From the original mess and duplicated code to this. Reactive programming and RxJS may have a steep learning curve, but once mastered can really tidy up your event based code. Thanks Ben and the RxJS team
Read MoreHandling EF Core migrations in a team
Entity Framework Core has a good migration story for the Database, however it’s not perfect, especially in a large team where DB schema changes are frequent. All migrations are increamental based on the last *DbContextModelSnapshot.cs snapshot. When multiple migrations are happening in different branches, a lot of issues arise and we even asked EF team on Twitter on how to solve this problem. Click here for the tweet
To better understand the problem, let’s build up a scenario in team project.
Let’s see an example of problematic migration:
A
/
B1 C
| |
B2 |
/
(git merge)
|
D
The migration B1 and B2 are out-of-sync of migration C and when we merge B1, B2 and C back into main branch the D migration will likely not work from correct DbContext model snapshot.
Traditionally, we would revert C migration, merge branch with B1 and B2 migrations and re-apply C migration with correct DbContext model snapshot. Git doesn’t help us resolving the problem because there are no conflicts from the code perspective.
I found that the best way to solve this problem is to treat *DbContextModelSnapshot.cs as binary file, forcing the developers to resolve the problem before they break migration in master branch.
By including .gitattributes in Migrations folder with content:
PageDbContextModelSnapshot.cs binary
This will prevent any non-incremental changes to *DbContextModelSnapshot.cs and prevents breaking migrations after merging into master branch.
You can check my friends blog post on how to improve your team communication and resolve conflicts, once they happen at his blog Overcoming EF Core Migration Conflicts.
Angular6 Runtime environment Variables
2018-05-31CODING ·angular environment-variables
Setting environment variables in your Angular application is pretty straight forward. But, what about when you want to set those variables at runtime depending on the environment you are running in?Out of the box, Angular provides a convenient method for setting environment variables for each our local dev environment and our production environment.
First we set our environment variables per environment:
environments/environment.ts
export const environment = {
production: false,
api: ‘https://localhost:1234/api’
};
environments/environment.prod.ts
export const environment = {
production: true,
api: ‘https://www.example.com/api’
};
And then when we build for Prod we simply need to tell webpack that we are building for prod and it performs a file substitution for us
../angular.json
“configurations”: {
“production”: {
“fileReplacements”: [
{
“replace”: “src/environments/environment.ts”,
“with”: “src/environments/environment.prod.ts”
}
]
},
}
ng build –prod
However, this solution doesn’t account for running in a CD (Continuous Deployment) pipeline. The problem lies in the fact that our build server will build the application only once for production, and then our CD tooling will move the build payload through our various environments: Dev, Stage, Test, Prod.
We could have an environment.*.ts file for each of my environments, then update my webpack config to substitute these files per environment.
BUT… We don’t really have the opportunity to rebuild the product for the intermediate environments. Besides even if we changed our process to handle this, this isn’t good practice anyway, as we should maintain a single payload through the entire process.
Put simply, what we are after is a way to configure the environment variables at runtime, not compile time. These days .NET Core handles this by retrieving the settings from appSettings.json (in the past web.config). Then we can then tell my deployment tooling (Octopus Deploy, VSTS, etc) to update this file each time the application is promoted through the pipeline.
To solve this problem in Angular, my first approach was to create an appSettings.json file, deploy it as an asset with my build, and have the existing environment.ts file(s) pull the values from here.
assets/environment.json
{
“api”: “https://localhost:1234/api”
}
environments/environment.ts
declare function require(url: string);
var json = require(‘./environment.json’);
export const environment = {
production: false,
api: json.api
};
Should this work, nothing else in my application needs to change
BUT This approach didn’t work, and I can only surmise that it is due to the fact that environment is exported as a const, and as such the values are evaluated at compile time (not runtime).
In search of an alternative method, I came across the following post from Juri Strumpflohner. A simple solution that solves my problems, but also allows me to take it a step further and fetch some/all of the variables from an external service.
The Solution
Create the environment.json file
assets/environment.json
{
“api”: “https://localhost:1234/api”
}
I place this file in the assets folder, to ensure it gets distributed with the application.
[optional] Define a model of the config file
models/environment.ts
export class environment {
api: string;
}
now config service that fetches the config
service/environment.services.ts
import { Injectable } from ‘@angular/core’;
import { HttpClient } from ‘@angular/common/http’;
import { appSettings } from ‘../models/appSettings’;
@Injectable()
export class EnvironmentService {
private appConfig : appSettings;
constructor(private http: HttpClient) { }
loadAppConfig() {
return this.http.get(‘/assets/environment.json’)
.toPromise()
.then(data = > {
this.appConfig = data as appSettings;
});
}
getConfig() : appSettings {
return this.appConfig;
}
}
Note: We are using the HttpClient to fetch the settings from a file. We could also call an endpoint to fetch the environment variables too
Note: As explained by Juri, The loadAppConfig() method needs to return a promise, as opposed to an Observable, as the API consuming this service currently only works with Promises.
Now for the magic: Configure the service to run as an app initialiser
import { NgModule, APP_INITIALIZER } from ‘@angular/core’;
import { EnvironmentService } from ‘services/environment.service.ts’;
@NgModule({
declarations: [
AppComponent
],
imports: [
HttpClientModule
],
providers: [
EnvironmentService,
{
provide: APP_INITIALIZER,
useFactory: (svc: EnvironmentService) = > { return () = > svc.loadAppConfig(); },
multi: true,
deps: [EnvironmentService]
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
Note: APP_INITIALIZER is an injection token that will instruct the application to run the factory method on application startup. In our case, this will result in calling our loadAppConfig() method
Note: ‘multi: true’ is required when providing APP_INITIALIZER. ‘multi: true’ tells the Dependency Injection container, that there can be more than one instance of the provided service (and to provide them all)
Update all references from environment to pull the settings from config.service
Finally, update my release definitions to perform the variable substitution for each environment.
Ng directive Thought Experiment
ngLet directive to resolve an async observable binding without having to use ngIf to hide contentNot long after publishing my recent post on minimising Async bindings in Angular by using the *ngIf=”obs$ | async as obs”; construct, I received the following comment on twitter:
it’s a pity they enabled the “let” option on ngif. what if i dont want to hide the section before the data arrive? May East (@mayeast) May 2, 2018
This got me thinking… Is there an existing way to do this? Is it possible? and even is it useful? After looking around into the ngIf documentation on Angular.io Structural Drirectives and then into the ngIf directive source/packages/common/src/directives/ng_if.ts, I figured it couldn’t be too hard to create a “Let” directive… As it turns out a Let directive is exactly the same as the ngIf directive without the “else“…
Acknowledgement: None of this is my code, I’ve just borrowed it from the existing ngIf directive
So let’s try
Step 1: Generate the directive
ng g directive ngLet
Step 2: Borrowing from ngIf, create an NgLetContext
export class ngLetContext {
public $implicit: any = null;
public ngLet: any = null;
}
Step 3: Declare and initialise local variables
a. The current context b. Reference to the template c. Reference to the currently embedded view
private _thenTemplateRef: TemplateRef | null = null;
private _thenViewRef: EmbeddedViewRef | null = null;
private _context: ngLetContext = new DhsLetContext();
Step 4: Inject into the constructor a “ViewContainerRef” and a “TemplateRef” and initialise our templateRef
a. The ViewContainerRef, is a reference to where our rendered view will be hosted
b. the TemplateRef, is a reference to the template within of our content
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) {
this._thenTemplateRef = templateRef;
}
Step 5: Define an Input property to accept the binding and update the view ref
@Input() set ngLet(condition: any) {
this._context.$implicit = this._context.ngLet = condition;
this._updateView();
}
Step 6: Update the current view
If there is a condition to evaluate, and we don’t already have a view, then ask our host container to create one based on our template and set the context.
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
if (this._thenTemplateRef) {
this._thenViewRef = this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
}
}
Step 7: Ensure the directive is declared in your module (this should be done for you if you used the cli to generate the directive for you)
@NgModule({
declarations: [
AppComponent,
NgLet
],
Final step: Put it to use
{{person?.name}}
Final directive class
import { Directive, ViewContainerRef, TemplateRef, Input, EmbeddedViewRef } from ‘@angular/core’;
@Directive({
selector: ‘[ngLet]’
})
export class LetDirective {
private _thenTemplateRef: TemplateRef | null = null;
private _thenViewRef: EmbeddedViewRef | null = null;
private _context: NgLetContext = new NgLetContext();
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) {
this._thenTemplateRef = templateRef;
}
@Input()
set ngLet(condition: any) {
this._context.$implicit = this._context.ngLet = condition;
this._updateView();
}
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
}
}
}
export class NgLetContext {
public $implicit: any = null;
public ngLet: any = null;
}
Although it is possible to create a “Let” directive, I’m not quite convinced on it’s usefulness at this stage, as each inner binding will require the ‘?.’ null check to avoid a ‘name not found on undefined” binding error and the issues associated with such binding errors. Either way, thanks for the challenge @mayeast
Read MoreAngular: arrrgh… too many async bindings
2018-05-01CODING ·angular rxjs async
Redundant async bindings will trigger excess invocations of your observable. This simple technique, lets you remove those excess subscriptionsIn my previous post I came across a quirk with Angular, Observables and the Async pipe. That is, each subscription to an observable will initiate the observables execution. Hence we were calling the api once per async subscription.
A subscribe call is simply a way to start an “Observable execution” and deliver values or events to an Observer of that execution.https://reactivex.io
Original version
{{ (result$ | async).name }}
{{ (result$ | async).surname }}
result$ = this.http.get(‘/api/person/1’);
My original attempt to resolve this, was to first subscribe to the observable in the typescript file, and then bind directly to the results property.
Attempt 1
{{ result.name }}
{{ result.surname }}
result: Person;
this.http.get(‘/api/person/1’).
subscribe(person = > this.result = person);
However, this now raises some more problems for us.
We get the following error: ERROR TypeError: Cannot read property ‘name’ of undefined. That is, before the http request returns and we set the result property, our bindings are throwing an error.
For every subscription to an observable, we need to manage the unsubscription. Normally Async pipes handle this for us.
To solve the first problem we have two options. We need to either: a) perform a null check on each binding
Attempt 2.1
{{ result?.name }}
{{ result?.surname }}
or b) wrap the bindings in a div and hide it with an *ngIf
Attempt 2.2
{{ result.name }};
{{ result.surname }}
Neither of these options help us with out second problem, the need to unsubscribe for each subscription. Enter this post from Todd Motto that I came across today, with a neat solution to both of the above problems.
Attempt 3
{{ result.name }}
{{ result.surname }}
Todd then offers a neat little progression to the above, to show an alternative template, when the above div is hidden
Final solution
{{ result.name }}
{{ result.surname }}
Loading Person…
Thanks, Todd, this simple hint will help tidy up a lot of my code.
Read MoreHow to get Angular logs to Seq
The Angular applications are starting to become and more complicated with more layers on the client side then ever before. This means we should treat client-side applications similarly as back-end projects.
For non-dev environments, I would recommend mature libraries like Raygun or TrackJS to track issues over time and be able to resolve them for specific versions.
However in development environment we can use a bit more chatty tool that gives us a lot more information about is happening with client-side application. As we all know, sometimes it’s more important how the user got to the error than the error itself.
For that I use JSNLog with Serilog + Seq. There are several JS to Seq libraries but I like this one the best because I can use exatly the same Serilog configuration with not much effort. (You can also use loggers like Structured log to directly log into Seq without the need of a backend)
The easiest way and not recommended despite being the part of tutorial, is to simply add the logging into app.module.ts:
import { NgModule, ErrorHandler } from ‘@angular/core’;
import { JL } from ‘jsnlog’;
// Logging stuff
class UncaughtExceptionHandler implements ErrorHandler {
handleError(error: any) {
JL().fatalException(‘Uncaught Exception’, error);
console.warn(‘logged’);
}
}
const logLevel = JL.getAllLevel();
const appender = JL.createAjaxAppender(‘example appender’);
appender.setOptions({
‘bufferSize’: 20,
‘storeInBufferLevel’: 1000,
‘level’: logLevel,
‘sendWithBufferLevel’: 6000,
‘url’: ‘https://localhost:51213/jsnlog.logger’
});
// Configure the JSNLog logging library.
// See https://jsnlog.com/Documentation/JSNLogJs
JL().setOptions({
‘appenders’: [appender],
‘level’: logLevel
});
JL().info(‘Angular is starting…’);
const appRoutes: Routes = [
// …
providers: [
{ provide: ‘JSNLOG’, useValue: JL },
{ provide: ErrorHandler, useClass: UncaughtExceptionHandler }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Server side:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
// Configure JSNLog
// See https://jsnlog.com/Documentation/Configuration/JSNLog
var jsnlogConfiguration = new JsnlogConfiguration()
{
// HACK: Format “%message” will break logging.
serverSideMessageFormat = “JL: %message”,
};
app.UseJSNLog(new LoggingAdapter(loggerFactory), jsnlogConfiguration);
}
Official instructions: https://jsnlog.com/Documentation/HowTo/Angular2LoggingMy oversimplified implementation based on instructions: https://github.com/jernejk/JSNLog/tree/features/jsnlog-simple
However this version will swallow all errors amoung other things if it fails to load on bootstraps, resulting in a white page and non-sense errors in console output.
I have improved the initial quick start to have LogService and UncaughtExceptionHandler in separate files, as well starting the JSNLog only when Angular essentials have been correctly bootstrapping. This solves the problem of the official quick start tutorial!
Since the code is too big, here is a diff between new application and adding log: https://github.com/jernejk/JSNLog/compare/features/jsnlog
Personally I use this all the way to UAT and disable it for preprod and production. I want the application to work as efficiently as possible once they hit production environments and unfortenately, JSNLog doesn’t work 100% well all of the time. All things consider, it’s an increadiable tool in development environement.
Remote debugging on Android and iOS devices
Every year the market share of mobile devices is growing, which means that testing on a mobile device is becoming increasingly more important.
While in easy cases we can open just open up the server to outside world and connect with your phone. However, in this blog I’ll tackle a much harder scenario:
WiFi is blocked and not accessible by mobile devices
Some of the resources are locked behind VPN, no mobile client for the VPN
Dev and staging servers are not available to mobile devices
The problem only happens on the mobile devices, specifically iPad
The blog post will still be very useful if you only have an issue with WiFi or VPN.
Software used
For enabling remote debugging in a locked down environment I used:
You can use any other applications that do the same functionality.
1. Create and connect to Wi-Fi hotspot
Create a hotspot and connect to hotspot via your mobile device.You can skip this part if you already can ping your laptop via the mobile device.
With Connectify it took me 2 minutes to do just that.Too easy, right?
On your mobile device use network utils that allows you to ping your laptop to see if the mobile device has access to the laptop.Connect to the VPN and repeat ping process to verify that the connection is still OK.
2. Configure IIS
Open applicationhost.config:
VS2015+
open [solution folder].vsconfigapplicationhost.config
Pre-VS2015
open C:Users[user-name]My DocumentsIISExpressconfigapplicationhost.config
Find bindings for your website, e.g.
And add a binding with your own IP address that is pingable through the mobile device.
Close your Visual Studio and run it as admin.The above change may require admin permissions in order to spin a new instance of IIS Express with the custom bindings.
If you don’t need to get all communication via VPN or don’t need to track all HTTP(S) requests, you’re done.If not, follow the last step.
Bonus: You can use Conveyor by Keyoti to simplify the process of enabling web apps to be accessible outside the local network.
3. Use Fiddler for debugging and enabling VPN on the mobile device
In this step, we’ll use Fiddler as a proxy that will redirect all the traffic from the mobile device directly to current network connection. You’ll be able to access everything your laptop can access via the phone, include the VPN connection.
You can follow this official guide to setup it up: Capture Traffic from iOS Device
Short version (Fiddler on desktop):
In Fiddler go to Tools | Options | Connections
Tick Allow remote computers to connect
Restart Fiddler
On iOS (similar steps for other mobile devices):
Go to Settings | General | Network > Wi-Fi
Select current Wi-Fi
On the bottom select Manual
Server: [laptops IP]
Port: 8888 (unless changed in Fiddler)
Authentication: Off
Now you should be able to access all of the laptop’s resources.
As a bonus, you can use Fiddler to manipulate traffic on your phone, simulate slow connections, modify packages, go offline or selectively cancel requests.
Bonus – Setup Angular CLI
When trying to run Angular CLI project instead of running ng serve run:
ng s –host [laptop IP] –port 4200
This will make the website available on the [laptop IP] address.
Update 24/08/2018: Added Angular CLI steps for remote debugging.
RxJS: Using CombineLatest() + StartWith() to remove code duplication
2018-03-26CODING ·angular rxjs
Using CombineLatest and StartWith (and DebounceTime) RxJS operators to remove code complexity and duplication when fetching paged search results05 June 2018 Update: Follow up post with further refactoring.
01 June 2018 Update: Updated code to RxJS6.
Scenario:
In my Angular5 application I have a search results page that contains three components:
Text-based filters component
Paged results list
Pagination component
After wiring up my code I ended up with something similar to below. This all worked fine, but as you can see it’s quite repetitive and not to mention hard to maintain.
results-list.component.html
results-list.component.ts
results$: Observable;
currentFilter: string = “Open”;
currentPage: number = 1;
readonly pageSize = 25;
onFilterUpdated(filter: string){
this.currentFilter = filter;
this.results$ = this.searchService.performSearch(this.currentPage,
filter, this.pageSize);
}
onPageUpdated(page: number){
this.currentPage = page;
this.results$ = this.this.searchService.performSearch(page,
this.currentFilter,this.pageSize);
}
ngOnInit(){
this.results$ = this.searchService.performSearch(this.currentPage,
this.currentFilter, this.pageSize);
}
Problem: Violation of DRY principle. Adding any extra form of triggering the search, will repeat the search line this.results$ = this.searchService.performSearch(this.currentPage, this.currentFilter);
Problem: If I want to subscribe to the results, I need to repeat the subscription each time I set this.results$. Don’t forget the associated unsubscription too.
Solution: Enter the combineLatest operator
When any observable emits a value, emit the latest value from each. https://www.learnrxjs.io
Next, we need the switchMap operator to finish off the combined observable and switch to the search observable
Map to observable, complete previous inner observable, emit values.https://www.learnrxjs.io
results-list.component.ts
results$: Observable;
@ViewChild(‘paginator’) paginator: PaginationComponent;
@ViewChild(‘filters’) filters: FilterComponent;
readonly pageSize = 25;
ngOnInit(){
const page$ = this.paginator.pageUpdated;
const filter$ = this.filter.filterUpdated;
this.results$ = combineLatest(page$, filter$, (p, f) = > {return {page: p, filter: f}}).pipe(
switchMap(r = > this.searchService.performSearch(r.page, r.filter, this.pageSize));
}
New Problem: This option doesn’t allow for an initial search on initialisation of the page.
Solution: enter the StartWith operator
Emit given value first.https://www.learnrxjs.io
results-list.component.ts
const page$ = this.paginator.pageUpdated.startWith(1);
const filter$ = this.filter.filterUpdated.startWith(”);
New Problem: Each binding on the UI to result$ resulted in a seperate call to the search service (even if we add a debounce in).
Solution: Subscribe to result and store results locally
Problem: Page number should reset to 1 when the filter updates
Solution: Store the current page, bind to it on the paginator and raise the event whenever the value changes. note: I’m still unsure if I’m 100% happy with this option
results-list.component.ts
const page$ = this.paginator.pageUpdated.pipe(
startWith(1),
tap(x= > this.currentPage = 1));
const filter$ = this.filter.filterUpdated.pipe(
startWith(”),
tap(x = > this.currentPage = x));
Bonus: Add a debounce to minimise extra calls to the service
Final code
results-list.component.html
.NET Core complex EF Core unit testing
When writing unit tests for EF Core we can use InMemoryDatabase so that we don’t have to create mock repositories. But sometimes we need to test functionalities that are not supported in in-memory-database.
[embedded content] In-memory-database does not support:
Executing SQL commands
Enforcing foreign keys
Returning null objects for properties that needed to be included in the query .Include()
Failing when using features not supported in Linq-to-sql queries
Of course, we can mock the data layer but sometimes we need to test if the database’s queries are playing well the application logic.
The solution is to use SQLite in-memory DB which allows us to use some of the SQL and DB relational features.
An example of creating DB context that switches between in-memory-database and SQLite database:
private bool _useSqlite;
public void UseSqlite()
{
_useSqlite = true;
}
public async Task GetDbContext()
{
DbContextOptionsBuilder builder = new DbContextOptionsBuilder();
if (_useSqlite)
{
// Use Sqlite DB.
builder.UseSqlite(“DataSource=:memory:”, x = > { });
}
else
{
// Use In-Memory DB.
builder.UseInMemoryDatabase(Guid.NewGuid().ToString()).ConfigureWarnings(w = >
{
w.Ignore(InMemoryEventId.TransactionIgnoredWarning);
});
}
var dbContext = new SampleDbContext(builder.Options);
if (_useSqlite)
{
// SQLite needs to open connection to the DB.
// Not required for in-memory-database and MS SQL.
await dbContext.Database.OpenConnectionAsync();
}
await dbContext.Database.EnsureCreatedAsync();
return dbContext;
}
Example of a unit test:
public async Task ShouldThrowAnException()
{
var context = GetDbContext();
context.TestDatas.Add(new TestData
{
Text = “Parent”,
IsDeleted = false,
Child = null
});
// Will not fail even though Child is required.
await context.SaveChangesAsync();
// Execute and assert
// This fails because in-memory-database does not support SQL.
await Assert.ThrowsAsync(
() = > context.TestDatas
.FromSqlRaw(@”select * from TestDatas”));
}
[Fact]
public async Task ShouldWork()
{
// Prepare
UseSqlite();
var context = GetDbContext();
context.TestDatas.Add(new TestData
{
Text = “Parent”,
IsDeleted = false,
Child = new ChildData
{
Text = “Child”
}
});
// Checks if Child property is correctly populated
context.SaveChanges();
// Execute
var data = await context.Database.GetDbConnection()
.QueryAsync(@”select * from TestDatas”);
// Assert
Assert.Single(data);
}
However, there are a few caveats regarding using SQLite in unit tests:
In-memory SQLite is around 5 3 times slower than EF Core in-memory-database
The order of results will not be consistent unless it’s ordered by a column
For complex database structures foreign key restrictions will not work and needs to be disabled in order to allow testing
If dealing with raw SQL commands, there are some differences between SQLite and MS SQL. It means that the code might need to generate slightly different SQL for SQLite than for MS SQL
The best approach so far is to choose in-memory-database and SQLite in-memory database based on the unit test scenario. Personally, I always prefer in-memory-database because of the performance benefits and I switch to SQLite when absolutely necessary.
Watch my DDD talk for more unit test content (DDD Sydney 2018)
[embedded content]
UPDATE: I’m going around Australia to present this topic on various user groups. Here is the GitHub repository for the session: https://github.com/jernejk/NorthwindTradersUPDATE 2: Updated the blog post and GitHub code for .NET Core 3.0.
SPA & ASP.NET Core – Part 2: Upgrading to Angular 5
This is the second in a series of posts about building Single Page Applications with ASP.NET Core 2.0. In this post, I’ll show you how to upgrade to Angular 5. …
The post SPA & ASP.NET Core – Part 2: Upgrading to Angular 5 appeared first on Jason Taylor.