Hello,
I’ve been learning F# for the past couple of weeks and I just encountered something quite unexpected.
Can someone explain why? I suspect it’s related to how FsUnit instantiates the test cases… but it also seems inconsistent with the semantic of the language.
Here’s the code:
open Xunit
open FsUnit
type Customer = { name: string }
module Test =
let j = { name = "John" }
[<Fact>]
let unexpectedNRE () = j.name |> should equal "John"
Running dotnet test results in the following error (exception line number corresponds to the last line above):
Test run for $PROJECT/Test/bin/Debug/net6.0/Test.dll (.NETCoreApp,Version=v6.0)
Microsoft (R) Test Execution Command Line Tool Version 17.1.0
[...clipped...]
Error Message:
System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
at EssentialFSharpApp.Test.Test.unexpectedNRE()
If I move let j = ... inside the test it works.
Removing the inner module Test makes no difference.
1 Like
Welcome @acorello!
I swear I’ve run into this exact problem a long time ago, but I’m having trouble remembering the details. Maybe it had to do with Xunit? I would try using NUnit to run your tests and see if that fixes it? Your file should be able to stay the same except open Xunit => open NUnit.Framework and [<Fact>] => [<Test>]
Hi @ntwilson, thank you!
I found and fixed the issue: the tests were in a separate xunit project and it worked after I put back the Program.fs file, which contained a default [<EntryPoint>] just returning zero. Without that file I get the NullReferenceException. It wasn’t obvious to me that file was required and my “no unnecessary code” policy took over. I don’t really understand what’s going on, but at least I can continue.
The problem happened also with NUnit.
Should anyone be interested, this is a repo with a passing test (tag: PASSING) and the failing tests after removing the Program.fs (tag: NRE), with both xunit and nunit.
Back to coding now 
FWIW, it seems to be related to the <GenerateProgramFile>false</GenerateProgramFile> property. If I delete that property, it seems that everything works fine. I’m not entirely sure what exactly that property is supposed to be doing for you (I usually am including tests in a library project instead of an executable project), but to me it seems like all it’s doing is making the test run fail with the NRE.
1 Like
I confirm, that removing GenerateProgramFile and the Program.fs entry from the .fsproj file I can run the tests without NRE.
I’m don’t know why the xUnit project is setup this way: I’ve followed the instructions and used the official template. It would make sense to me to have a “lib” project rather than an “exe” project, or to set GenerateProgramFile to true (the default) and remove Program.fs: the current Program.fs is just a stub anyway; it would be less code.
If I wanted to use that approach would I run dotnet new classlib -lang F# then add the dependencies?
Yep. That’s exactly what I end up usually doing. I have some mistrust for the templates, because they’re not always up-to-date, so I usually start with dotnet new classlib -lang f# and go from there.
But also dotnet new classlib -lang f# vs dotnet new nunit -lang f# vs dotnet new console -lang f# are all just differences in the .fsproj file. The difference between a “classlib” and a “console” is the <OutputType> property in the .fsproj - one would be <OutputType>Library</OutputType>, and the other <OutputType>Exe</OutputType>. (And the “nunit” or “xunit” projects would have some extra dependencies and properties). So you can switch between them without creating a new project just by editing your .fsproj file.
1 Like
Thank you for asking the question and pasting the solution. I was tackling the problem for 5 days. Apparently without entrypoint it is it is hard for F# console app to load and properly initialize. Therefore it should not be skipped, for non library packages.
This is what gpt said, after I provided it solution that I got from this response. so Thank you again ntwilson acorello
Why Adding EntryPoint Helped
When you added the EntryPoint function, you effectively told the runtime:
- “This is where the application should start.”
- This allowed the entire application, including all modules (like
InteractionDto), to be loaded and initialized correctly. and Without the entry point, the runtime may have had trouble fully initializing the application and ensuring that all functions, values, or modules were loaded into memory. This could result in null references when trying to access modules or functions that weren’t fully initialized.
I was only testing and not running it. Therefore, I was thinking about C# and F# interop issue. Probably, running the app would’ve hinted to this earlier.