Continuous Integration for .NET with Travis CI and xUnit

For this post, I’m going to go over getting a simple CI environment setup using Travis CI and xUnit.

If you’re one of those people that just wants to see the code, click here!

Our goal is to have the following scenario: whenever a push happens (to any branch) on GitHub, a build of the app will be triggered by Travis CI. If the build succeeds, it will run all of the unit tests for your app. If those all pass, the build is successful, otherwise it is considered a failed build. The build process will make use of NuGet to restore all packages prior to build.

I’m going to assume we’re starting with a brand new GitHub repo. In my case, I created a new repo with C# as the chosen language and MIT for the license. After cloning, I have the following directory structure:


Because the goal here is to illustrate the process rather than the app, We're going to make a really simple app. I would strongly suggest setting up CI as early on as possible on your projects as well. That way, when build issues arise you can solve them one at a time (way easier).

Creating the Projects

Let's create the application. In Visual Studio...

We need to use .NET 4 for all projects in the solution. This is due to mono's lack of support for .NET 4.5.

You should now have the following structure:

    CI Demo.sln

Making it Work Locally

The CI.Demo.Test Project

Now add a new class called Feature.cs with the following code:

The CI.Demo Project

Now that we've got a few tests, let's make it work. Add the Feature.cs class with the following implementation:

The last step here is to enable NuGet package restore. Right-click on the solution and select "Enable NuGet Package Restore." This will create the src/.nuget folder and add a few files to it.

At this point, the solution should compile and the tests should pass. This would be a good time to commit. Be sure that the .nuget folder is committed to git

Adding xUnit Dependency Files

In order to run xUnit tests from the Travis CI server, we will need to supply it with the path to the console runner.

For this we need to download xunit from and copy the following files into <path/to/app>/lib/xunit/

You should now have the following directory structure:

      ...files we just added

Setting Up NuGet Package Restore for Mono

Because we're using mono (Travis uses mono), we need to add a DLL to the .nuget folder in order for the msbuild task to complete successfully. We also need to handle a weird formatting issue with NuGet.targets. This work will be done in </path/to/app/src/.nuget/.

Adding Dependecies

Let's start by downloading the Microsoft.Build.dll assembly and adding it to the </path/to/app>/src/.nuget/ directory.

Creating Mono Target File

Now we need to make a mono-specific version of the NuGet.targets file. Make a copy of NuGet.targets and save it as NuGet.mono.targets. We need to modiy the RestoreCommand value by removing the literal space in the -solutionDir parameter.

Original: -solutionDir "$(SolutionDir) "

Updated: -solutionDir "$(SolutionDir)"

I know...this is silly right? Unfortunately the space is required on Windows (at least with my set up), but it can't be there for 'nix systems. In the future, I intend to have the Travis script modify the existing file so we don't need two...but this is simpler for now.

The Travis YAML file

We need Travis to do the following whenever a build is triggered:

A lot right? It turns out this is pretty straight forward. Create a new file called .travis.yml (notice the leading dot) in </path/to/app/>.

Add the following to it:

Now we commit our changes and push to GitHub

1 git add .
2 git commit -am "setting up for travis integration"
3 git push

Making GitHub and Travis Hold Hands

At this point, all we need to do it tell Travis CI about our repo. Super simple...

Now that it's all setup, we're going to add the build status to the file and push to GitHub. This will trigger the build.

Add the following to your file (replace pseudomuto/travis-ci-demo with your GitHub repo)

Final Steps

I've put the complete source code for this on GitHub for reference. Feel free to fork and make changes...