.NET Testing in Visual Studio Code

After some experimentation, I found a local testing pattern that made sense to me. For this case, the application is targeted to be run as a container using .net core 3.1 on linux. It’s a basic crud app and I needed a local database which I wanted to manage using a container as well. Additionally, I was wanting to use source control for managing the database versioning as well.

Below is the launch.json for the solution.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (web)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build_compose",
            "program": "${workspaceFolder}/PowerMeterApi/bin/Debug/netcoreapp3.1/PowerMeterApi.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Local",
                "ASPNETCORE_ConnectionStrings__MeterDbConnectionString": "Server=localhost;Database=db_name;User Id=sa;Password=Pass1234"
            },
            "sourceFileMap": {
                "/Views": "${workspaceFolder}/Views"
            },
            "postDebugTask": "docker-compose-down"
        }
    ]
}

The two main changes to get things working the way I wanted them to was the preLaunchTask and the postDebugTask. These two attributes take one of the tasks from the tasks.json and executes it in a lifecycle. However, the preLaunchTask was where I needed something extra – it only takes a single task as an argument. I need to actually do 2 things before starting to debug. I need to build and start the database container.

Luckily, the tasks can be chained together using dependsOn.

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "process",
            "args": [
                "build",
                "${workspaceFolder}/PowerMeterApi.Tests/PowerMeterApi.Tests.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "publish",
            "command": "dotnet",
            "type": "process",
            "args": [
                "publish",
                "${workspaceFolder}/PowerMeterApi.Tests/PowerMeterApi.Tests.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "watch",
            "command": "dotnet",
            "type": "process",
            "args": [
                "watch",
                "run",
                "${workspaceFolder}/PowerMeterApi.Tests/PowerMeterApi.Tests.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "wait10",
            "type": "shell",
            "command": "sleep",
            "args": [
                "10"
            ]
        },
        {
            "label": "docker-compose-up",
            "command": "docker-compose -f docker-compose.yml up -d --build",
            "type": "shell"
        },
        {
            "label": "docker-compose-down",
            "command": "docker-compose -f docker-compose.yml down",
            "type": "shell"
        },
        {
            "label": "build_compose",
            "dependsOn": [
                "build",
                "docker-compose-up",
                "wait10"
            ]
        }
    ]
}

The build_compose step is the one I targeted. It combines the build step and the execution of docker-compose up. But you’ll notice the “wait10” step as well. I needed a hack to get around the docker-compose behavior. If you execute docker-compose up without the ‘-d’ argument, it executes in the current shell and doesn’t exit automatically. If you use the ‘-d’, it will run in a forked shell and in the background. Running it in the foreground means it will never finish the step. Running it in the background means the step will finish quickly and the app will start without the database running. So as a good developer, instead of actually figuring out a way to signal that the database is ready prior to the execution of the app, I just put a wait in there.