Starting with .NET 8.0, we have a better alternative for testing logging and logging messages. We don’t need to roll our own mocks anymore. Let’s learn how to use the new FakeLogger<T> inside our unit tests.
.NET 8.0 introduces FakeLogger, an in-memory logging provider designed for unit testing. It provides methods and properties, such us LatestRecord, to inspect the log entries recorded inside unit tests.
Let’s revisit our post on unit testing logging messages. In that post, we used a Mock<ILogger<T>> to verify that we logged the exception message thrown inside a controller method. This was the controller we wanted to test,
usingMicrosoft.AspNetCore.Mvc;namespaceFakeLogger.Controllers;[ApiController][Route("[controller]")]publicclassSomethingController:ControllerBase{privatereadonlyIClientService_clientService;privatereadonlyILogger<SomethingController>_logger;publicSomethingController(IClientServiceclientService,ILogger<SomethingController>logger){_clientService=clientService;_logger=logger;}[HttpPost]publicasyncTask<IActionResult>PostAsync(AnyPostRequestrequest){try{// Imagine that this service does something interesting...await_clientService.DoSomethingAsync(request.ClientId);returnOk();}catch(Exceptionexception){_logger.LogError(exception,"Something horribly wrong happened. ClientId: [{clientId}]",request.ClientId);// ^^^^^^^^// Logging things like good citizens of the world...returnBadRequest();}}}// Just for reference...Nothing fancy herepublicinterfaceIClientService{TaskDoSomethingAsync(intclientId);}publicrecordAnyPostRequest(intClientId);
Let’s test the PostAsync() method, but this time let’s use the new FakeLogger<T> instead of a mock with Moq.
To use the new FakeLogger<T>, let’s install the NuGet package: Microsoft.Extensions.Diagnostics.Testing first.
Here’s the test,
usingFakeLogger.Controllers;usingMicrosoft.Extensions.Logging;usingMicrosoft.Extensions.Logging.Testing;// ^^^^^usingMoq;namespaceFakeLogger.Tests;[TestClass]publicclassSomethingControllerTests{[TestMethod]publicasyncTaskPostAsync_Exception_LogsException(){varclientId=123456;varfakeClientService=newMock<IClientService>();fakeClientService.Setup(t=>t.DoSomethingAsync(clientId)).ThrowsAsync(newException("Expected exception..."));// ^^^^^// 3...2...1...Boom...// Look, ma! No mocks here...varfakeLogger=newFakeLogger<SomethingController>();// ^^^^^varcontroller=newSomethingController(fakeClientService.Object,fakeLogger);// ^^^^^varrequest=newAnyPostRequest(clientId);awaitcontroller.PostAsync(request);// Warning!!!//var expected = $"Something horribly wrong happened. ClientId: [{clientId}]";//Assert.AreEqual(expected, fakeLogger.LatestRecord.Message);// ^^^^^^^^// Do not expect exactly the same log message thrown from PostAsync()// Even better:fakeLogger.VerifyWasCalled(LogLevel.Error,clientId.ToString());// ^^^^^}}
We needed a using for Microsoft.Extensions.Logging.Testing. Yes, that’s different from the NuGet package name.
We wrote new FakeLogger<SomethingController>() and passed it around. That’s it.
2. Asserting on FakeLogger
The FakeLogger<T> has a LatestRecord property that captures the last entry we logged. Its type is FakeLogRecord and contains a Level, Message, and Exception. And if no logs have been recorded, accessing LatestRecord will throw an InvalidOperationException with the message “No records logged.”
But, for the Assert part of our test, we followed the lesson from our previous post on testing logging messages: do not expect exact matches of logging messages in assertions. Otherwise, any change in the structure of our logging messages will make our test break, even if the underlying business logic remains unchanged.
Instead of expecting exact matches of the logging messages, we wrote an extension method VerifyWasCalled(). This method receives a log level and a substring as parameters. Here it is,
publicstaticvoidVerifyWasCalled<T>(thisFakeLogger<T>fakeLogger,LogLevellogLevel,stringmessage){varhasLogRecord=fakeLogger.Collector// ^^^^^.GetSnapshot()// ^^^^^.Any(log=>log.Level==logLevel&&log.Message.Contains(message,StringComparison.OrdinalIgnoreCase));// ^^^^^if(hasLogRecord){return;}// Output://// Expected log entry with level [Warning] and message containing 'Something else' not found.// Log entries found:// [15:49.229, error] Something horribly wrong happened. ClientId: [123456]varexceptionMessage=$"Expected log entry with level [{logLevel}] and message containing '{message}' not found."+Environment.NewLine+$"Log entries found:"+Environment.NewLine+string.Join(Environment.NewLine,fakeLogger.Collector.GetSnapshot().Select(l=>l));thrownewAssertFailedException(exceptionMessage);}
First, we used Collector and GetSnapshot() to grab a reference to the collection of log entries recorded. Then, we checked we had a log entry with the expected log level and message. Next, we wrote a handy exception message showing the log entries recorded.
Voilà! That’s how to write tests for logging messages using FakeLogger<T> instead of mocks.
If we only want to create a logger inside our tests without asserting anything on it, let’s use NullLogger<T>. But, if we want to check we’re logging exceptions, like good citizens of the world, let’s use the new FakeLogger<T> and avoid tying our tests to details like the log count and the exact log messages. That makes our tests harder to maintain. In any case, we can roll mocks to test logging.
I ran an experiment. Maybe it was fear of missing out. I decided to use AI to help me launch a new course. This is how I used Copilot and the prompts I used.
I got this idea after watching one of Brent Ozar’s Office Hours videos on YouTube where he shared he keeps ChatGPT opened all the time and uses it as a junior employee. I decided to run a similar experiment, but for launching a new course on unit testing, one of my favorite subjects.
1. Lesson content and materials
I planned the lesson content and recorded and edited all video lessons myself. #madebyahuman
For the editing part, I used Adobe Podcast to remove background noise. My neighbor’s dog started to bark every time I hit record. And the other day at home, somebody made a smoothie with a loud blender while I was recording. Arrrggg! The only downside is that my voice sounds auto-tuned, especially in word endings.
I used Copilot for its convenience. I don’t need to create an account. Even I made Microsoft Edge open Copilot as the default tab. I only need to press the Windows key, type “Edge,” and I’m right there.
These are the prompts I used.
Generate a list of title ideas for my course
You’re an expert on course creation, programming, and SEO, give me a list of titles for a course to teach insert subject here
Rewrite my draft for a landing page
Now you’re an expert on online writing, SEO, marketing, and copywriting, please help me improve this landing page for an online course to increase sales and conversions. Make sure to use a friendly and conversational tone.
This is my landing page:
insert landing page here
Turn a landing page into a script for an introductory video
Now turn that last version of the landing page into a list of paragraphs and sentences I can use to create a PowerPoint presentation. Keep it short and to the point. Use only 10 paragraphs. I will turn each paragraph into a slide
Once I got all the lesson content and a landing page ready, I moved to the promotion part.
These are the prompts I used.
Write an email inviting readers to buy this new course
You’re an expert on copywriting and email marketing, give me n ideas for a short email to invite a reader who already took some action to buy my new video course: insert course name here. In that course, I insert brief description here. Offer a promo code for a limited time. Use friendly and engaging language.
Write a call to action for my posts
You’re an expert copywriter, give me n ideas for a two-sentence paragraph to promote my new course insert course name here. I’d like to use that paragraph at the end of posts on my blog to invite my readers to join the course. Use a friendly and conversational tone.
Write a launching post on LinkedIn
You’re an expert on copywriting, LinkedIn, and personal branding. Give me n ideas for a LinkedIn post to promote my new course insert title here based on the landing page of the course. Use a friendly and conversational tone.
This is the course landing page of the course:
insert landing page here
Nothing fancy. I followed the pattern: “Act as X, do Y for me based on some input. This is the input.”
I don’t use the exact same words Copilot gives me. I change the words I don’t use to make it sound like me.
Voilà! That’s how I used AI, especially Copilot, to help me launch my new testing course. On a Saturday morning, I ended up with a landing page and the script for an intro video for the course. What a productive morning!
I use AI the same way Jim Kwik, the brain coach, recommends in one of his YouTube videos: “AI (artificial intelligence) to enhance HI (human intelligence), not to replace it.” I don’t want AI to take out the pleasure of doing what I like to do. It’s my assistant.
By the title, I guess you infer this isn’t a book about software engineering or programming. It’s about leadership. By any means, I’m advocating for war. But I think the military has to teach a lot about management and leadership. For years, they have been leading large organizations with complex tasks in changing environments. That sound a lot like software engineering, right?
Extreme Ownership is a book about the stories of two Navy SEALs officers and the lessons they learned while in service. In every chapter, they tell a story about their time in service, the leadership principle behind it, and its application to the corporate world. It’s an easy read. Probably, you could finish it in a weekend, too.
Extreme Ownership is the principle that a leader “owns” or is responsible for everything that happens to the mission or his team. Even to take the blame when things go wrong. “The leader is truly and ultimately responsible for everything.”
Leaders, who accept their responsibility when things go wrong, inspire respect and trust. They show and teach that attitude to the team.
A leader is responsible even for the underperformers. His job is to train and mentor them to level up their skills.
A leader should lead “up” too. He should speak up and ask questions when things could be better. He is also responsible for the communication with his own leaders.
A leader should create a simple plan and make sure everyone understands it clearly. A leader should first understand and believe in the mission. And when priorities change, he should communicate those changes and pass “situational awareness.”
A leader should put a system and mentor people so he can be the “tactical genius” looking at the bigger picture.
Voilà! Those are some of my takeaways. The book uses memorable stories to tell these principles. I like how the authors extrapolated the principles they learned while on duty to the corporate world. It turns out that the authors, as SEAL officers, had to deal with some of the same bureaucracy from the corporate world too. Even PowerPoint presentations! PowerPoint presentations! I didn’t see that coming.
As someone who influences decision-making inside teams, I enjoyed the book, and I will take lots of these ideas into my daily activities.
I’d like to close with this quote from the book: “there are no bad teams, only bad leaders.”
I like the idea of keeping “lab” notebooks, especially to record the thought process for consulting clients. I’m a big fan of plain text. But the article recommends using pen and paper.
Git and Version Control are one of the subjects we have to learn ourselves. I prefer short Pull Requests with only one or two commits. I name my commits with a long and descriptive title that becomes a good title on PR descriptions.
This article shows “tactics” to better use Git. I really like these two:
start by writing the commit message before the actual work and then amending it. Commit Message Driven Development I guess, to follow the XDD trend
splitting refactorings into two commits: changing the API and then changing the calling sites
10 Things Software Developers Should Learn about Learning
This article contains ten findings, backed by research, about learning and software engineering. Totally worth reading if you’re into learning about learning.
These are some of my favorite lessons:
“Expert programmers may have low or high working memory capacity but it is the contents of their long-term memory that make them experts.”
“Expert developers can reason at a higher level by having memorized (usually implicitly, from experience) common patterns in program code, which frees up their cognition.”
“The brain needs rest to consolidate the new information that has been processed so that it can be applied to new problems.”
“There is a key distinction between a beginner who has never learned the details and thus lacks the memory connections, and an expert who has learned the deeper structure but searches for the forgotten fine details.”
“There is no reliable correspondence between problem-solving in the world of brain teasers and problem-solving in the world of programming. If you want to judge programming ability, assess programming ability.”
If you don’t want to read the whole article, jump to the Summary section at the end.
Why Most Software Engineering KPIs are BS and What to Do About it in 2024
Does your team measure story points too? The truth is we don’t know how to measure our work. I worked with a company that measured the percentage of completed tasks. It was a killing pressure. And once we have a metric, we start to game it. On the last day of the sprint, we saw people removing tasks so as not to mess with the final percentage.
From the article: “Engineering leaders (and sometimes non-technical executives) have often made the poor choice to measure output rather than impact”. The author recommends: “separate engineering health metrics to KPIs.” This is how the team feels vs the business impact of the work done.
27 Years Ago, Steve Jobs Said the Best Employees Focus on Content, Not Process.
This article tells Steve Jobs’s opinion on office politics and bureaucracy and how often we think “paperwork” is the real work. Process vs Output. Also, this article shares how to keep the best team members around, those who are good at identifying the output.
From the article: “Success is rarely based solely on process. Success mostly comes down to what your business accomplishes.”
Voilà! What are the KPIs of your team? Do you measure story points or percentage of completed vs planned tasks? Until next Monday Links.
Want to receive an email with curated links like these? Get 4 more delivered straight to your inbox every Friday. Don’t miss out on next week’s links. Subscribe to my Friday Links here.
Leadership and communication are more important than coding for the success of a project.
Last year, I worked as an independent contractor and engaged in short projects with an American client. This project was a six-month effort to bring group events, like weddings, conferences, and retreats, to a property management system.
This was one of the projects that convinced me that unclear expectations and poor communication kill any software project, not libraries and tools.
These are the two lessons I learned from this project.
Lesson 1: Have uncomfortable conversations earlier
Inside our team, we all started to notice “certain” behavior in a team member.
He delayed Pull Requests for minor things, interrupted meetings with off-topic questions, and asked for 100% detailed and distilled assignments.
Even once, he made last-minute changes, blocking other team members before a deadline. It wasn’t a massive unrequested refactoring, but it frustrated some people.
From the outside, it might have appeared he was acting to jeopardize the team’s progress.
It was time to virtually sit and talk to him. But nobody did it. And things started to get uncomfortable, eventually escalating the situation to upper management, leading to a team reorganization. There was “no chemistry with the team.”
An empathetic conversation could have clarified this situation. Maybe the “problematic” team member had good intentions, but the team misinterpreted them. Who knows?
This whole situation taught me to have uncomfortable conversations earlier.
“You only have to add your changes to this existing component. It’s already working.”
I bet you have heard that, too. That was what our Product Owner told us to extend an existing feature. We needed to import an Excel file with a list of guests into a group event.
The next thing we knew was that the already-working component had issues. The original team was laid off, and we couldn’t get our questions answered or count on them to fix those issues. We were in the dark.
What was a simple coding task turned out to be a longer one. Weeks later, we were still fixing issues and closing tickets. Nothing related to our task.
Before starting to work on top of an “already-working” feature, I learned to test it and give a list of existing issues. Otherwise, those existing issues will appear as bugs in our changes. And people will start asking questions: “Why are you taking so much time on this? It’s a simple task. It was already working.”
After that time, I realized something similar had happened in every job I’ve been in. I didn’t see that coming in this project.
Lesson learned! Once you touch it, you own it!
Voilà! These are the lessons I learned from this project: have uncomfortable conversations and test already-working features. Also, this project made me think it’s better to hire a decent developer who can be mentored and coached than a “rock star” who can’t get along with the team.